目次


共通テーマ

実例でわかる sed 第 2 回

UNIX テキスト・エディターのより有効な利用法

Comments

コンテンツシリーズ

このコンテンツは全#シリーズのパート#です: 共通テーマ

このシリーズの続きに乞うご期待。

このコンテンツはシリーズの一部分です:共通テーマ

このシリーズの続きに乞うご期待。

Sedは大変便利な (しかし、しばしば忘れ去られている) UNIXストリーム・エディターです。ファイルのバッチ編集や、既存のファイルを効果 的な方法で変更するためにシェル・スクリプトを作成する際には、最適です。この記事は前回のsedの紹介記事がベースになっています。

文字ストリングの置換

さてsedの非常に便利なコマンドである、置換コマンドを調べてみましょう。このコマンドを使うと、特定の文字ストリングや正規表現と一致した文字ストリングを、別 の文字ストリングで置き換えることができます。置換コマンドの最も基本的な使用例です。

$ sed -e 's/foo/bar/' myfile.txt

上のコマンドは、myfile.txtの内容をstdoutに出力します。その際、それぞれの行で最初に見つかった 'foo' (もしあれば) をストリング 'bar' で置き換えます。わざわざそれぞれの行の最初に見つかった と言っていることに注意してください。しかし、そのようなことは普通しません。文字ストリングを置換する場合、行全体に対し置換操作をするのが一般 的です。それぞれの行のすべての 対象ストリングを置き換えるためには、次のようにします。

$ sed -e 's/foo/bar/g' myfile.txt

最後のスラッシュの後に付加されている 'g' オプションが一括置換を指示しています。

この他に 's///' 置換コマンドについて知っておくと便利なことがいくつかあります。第一に、それは1つのコマンドであり、コマンド以外には何も存在しないということです。上のどちらの例でもアドレスは何も指定されていません。つまり、's///' コマンドをアドレスと共に使用して、コマンドが適用される行をコントロールすることができるわけです。次のようになります。

$ sed -e '1,10s/enchantment/entrapment/g' myfile2.txt

この例は、1行目から10行目までに出てくるすべての 'enchantment' を、'entrapment' で置き換えます。

$ sed -e '/^$/,/^END/s/hills/mountains/g' myfile3.txt

この例は、ブランク行から、'END' の3文字で始まっている行までのテキスト・ブロックにある 'hills' だけを 'mountains' に置き換えます。

's///' commandのもう一つの良い点は、'/' セパレーターに関するオプションが豊富にあることです。文字ストリングの置換を実行しようとしていて、正規表現や置換する文字ストリングの中にたくさんのスラッシュがある場合、's' の後ろに別の文字を指定することにより、セパレーターを変更することができます。たとえば、これはすべての /usr/localを /usrで置き換える例です。

$ sed -e 's:/usr/local:/usr:g' mylist.txt

この例ではセパレーターにコロンが使われています。正規表現の中で、セパレーター文字を指定する必要があるときは、その前に円記号 (\) を入れます。

正規表現の理解の混乱

これまでは、単純な文字ストリングの置換だけを見てきました。それだけでも何かと便利ですが、さらに正規表現と一致させることもできます。たとえば、次のsedコマンドは、'<' 記号で始まり、間に何文字かあって、'>' 記号で終わっている文字列と一致します。この文字列は削除 (空ストリングと置換) されます。

$ sed -e 's/<.*>//g' myfile.html

これは、ファイルからHTMLタグを除去するsedスクリプトの最初の試みとしては良くできていますが、正規表現の特殊な性質のため、これはうまく働きません。なぜでしょうか。sedは一つの行で正規表現と一致する文字列を捜す時、その行にある一番長い 文字列を見つけ出します。これは前回のsedの記事での問題点とは違います。なぜなら前のは 'd' コマンドや 'p' コマンドを使っているケースであり、行全体を削除したり出力するものだからです。しかし、's///' コマンドを使った場合は、明らかに大きな違いがあります。なぜなら正規表現が一致する部分だけが、ターゲット文字ストリングで置き換えられるか、このケースのように削除されるからです。このような理由で、上の例を

<b>This</b> is what <b>I</b> meant.

に実行すると、結果は次のようになります。

meant.

しかし望んでいる結果はこれではなく、次のものです。

This is what I meant.

さいわいこの問題は簡単に解決できます。「'<' 記号で始まり、間に何文字かあって、'>' 記号で終わっている」という正規表現の代わりに、「'<' 記号で始まり、'>' 記号ではない文字が続き、'>' 記号で終わっている」という正規表現を指定するだけでよいのです。この結果、一番長いものではなく、一番短いのものと一致させることができます。新しいコマンドはこうなります。

$ sed -e 's/<[^>]*>//g' myfile.html

上の例では、'[^>]' が「'>' ではない」文字です。その後の '*' で長さ0、または長さ1以上の「'>' を含まない文字列」の意味になります。このコマンドをいくつかのhtmlファイルでテストし、さらにシェル・スクリプトにパイピングして、結果 を検証してみてください。

文字の一致の補足

'[ ]' 正規表現の構文には、他にも付加オプションがあります。文字の範囲を指定するには、'-' を使用します。'-' を先頭や最後に使うことはできません。

'[a-x]*'

これは、長さ0、または長さ1以上の、'a','b','c'...'v','w','x' だけを含む文字列と一致します。さらに、'[:space:]' 文字クラスを、空白文字と一致させるために使うことができます。次のリストは使用できる文字クラス全部の一覧表です。

文字クラス説明
[:alnum:]英数字 [a-z A-Z 0-9]
[:alpha:]英字 [a-z A-Z]
[:blank:]スペースまたはタブ
[:cntrl:]制御文字
[:digit:]数字 [0-9]
[:graph:]すべての可視文字 (空白文字でないもの)
[:lower:]小文字 [a-z]
[:print:]非制御文字
[:punct:]句読文字
[:space:]空白文字
[:upper:]大文字 [A-Z]
[:xdigit:]十六進数字 [0-9 a-f A-F]

使える時はできるだけ文字クラスを使う方が好都合です。なぜなら文字クラスは、英語を母国語としない地域により適しているからです。(必要に応じアクセント付き文字が含まれたりするなどの理由による。)

拡張置換について

単純なものと適度に複雑である直接指定された置換を見てきましたが、sedはもっといろいろなことができます。実際、一致した正規表現の一部、あるいは全体を参照することができます。そして参照部分を使って、置換後のストリングを作りだすことができます。たとえば、メッセージに回答しているものとしましょう。次の例は、おのおのの行に、"ralph said:" という文字ストリングを先頭に付加します。

$ sed -e 's/.*/ralph said: &/' origmsg.txt

出力はこうなります。

ralph said: Hiya Jim,
ralph said:
ralph said: I sure like this sed stuff!
ralph said:

この例では、置換後のストリングに '&' 記号が使われています。これは一致した正規表現全体を挿入させる指示です。そこで '.*' と一致したものなら何でも (その行の長さ0、または長さ1以上の文字の最大グループ、あるいは行全体)、それを置換後のストリングの任意の場所に、何回でも挿入することができます。これだけでもすごいのですが、sedはもっとパワフルです。

すばらしい、円記号付き括弧

'&' 記号より便利なものは、's///' コマンドで正規表現内に領域 を定義できることです。この領域を置換後のストリングの中で参照することができます。たとえば、次のテキストが含まれているファイルがあるものとします。

foo bar oni
eeny meeny miny
larry curly moe
jimmy the weasel

さて、"eeny meeny miny" を "Victor eeny-meeny Von miny" に置き換え、他の行も同様に変更するsedスクリプトを書いてみましょう。このためにまず、スペースで分かれている3個の文字ストリングと一致する正規表現を書きます。

'.* .* .*'

こうです。次に、対象とする各領域の前後に円記号の付いた括弧を挿入して、領域を定義します。

'\(.*\) \(.*\) \(.*\)'

この正規表現は、最初のものと同じ働きをします。違う点は3個の論理領域が、置換後のストリングの中で参照できるように定義されていることです。スクリプトは最終的に次のようになります。

$ sed -e 's/\(.*\) \(.*\) \(.*\)/Victor \1-\2 Von \3/' myfile.txt

お分かりのように、おのおのの括弧で区切られた領域が、'\x' (xは1から始まる領域番号) で参照されています。出力は次のようになります。

Victor foo-bar Von oni
Victor eeny-meeny Von miny
Victor larry-curly Von moe
Victor jimmy-the Von weasel

sedに精通するにつれて、ほんの少し努力するだけで、相当パワフルなテキスト処理をおこなえるようになります。お気に入りのスクリプト言語では、この問題をどんな方法で解決していたのか考えてみましょう。一行のソリューションを簡単に用意できていたでしょうか。

複数のコマンドを指定する

より複雑なsedスクリプトの作成を始めるには、複数のコマンドを入力する方法を知っておく必要があります。これには何通 りかの方法があります。まず、コマンドとコマンドの間にセミコロンを使うことができます。たとえば、次の連続したコマンドでは '=' コマンドと 'p' コマンドが使われています。'=' コマンドは、行番号の出力を指示します。一緒に使われている 'p' コマンドは行を出力するよう明示的に指示します。(-nモードが指定されているため)

$ sed -n -e '=;p' myfile.txt

2個以上のコマンドが指定されている場合、ファイルのどの行にも各コマンドが (順番に) 適用されます。上の例では、まず '=' コマンドが1行目に適用され、次に'p' コマンドが適用されます。上の例では、まず '=' コマンドが1行目に適用され、次に 'p' コマンドが適用されます。それから2行目に進み、処理を繰り返します。セミコロンは手軽である一方、使えない場合もあります。別 の方法として、2個の -eオプションを使って、2つの別々のコマンドを指定する方法があります。

$ sed -n -e '=' -e 'p' myfile.txt

しかしながら、もっと複雑な追加や挿入コマンドを扱う場合には、複数の '-e' オプションでさえも役に立ちません。複雑で複数行になるスクリプトでは、コマンドを一つの別 なファイルに入れるのが最善の方法です。そして -fオプションを使ってこのスクリプト・ファイルを参照します。

$ sed -n -f mycommands.sed myfile.txt

この方法は、多少不便だといえますが、どんな場合でも使えます。

1アドレスを複数コマンドで

一つのアドレスを複数のコマンドに適用したい場合が時々あります。これはたくさんの 's///' コマンドを実行して、ワードを形成したり、ソース・ファイルの文法規則に合わせるときに、特に手軽に使われます。アドレスごとに複数のコマンドを実行するには、sedコマンドをファイルに入れ、次のように '{ }' 記号を使ってコマンドをグループ化します。

1,20{
	s/[Ll]inux/GNU\/Linux/g
	s/samba/Samba/g
	s/posix/POSIX/g
}

上の例では、3個の置換コマンドが1行目から20行目に適用されます。正規表現によるアドレスや、行番号と正規表現が混在したアドレスを使うこともできます。

1,/^END/{
        s/[Ll]inux/GNU\/Linux/g s/samba/Samba/g s/posix/POSIX/g 
	p
}

この例では、'{ }' の間のすべてのコマンドを、1行目から "END" で始まる行までの行に適用します。"END" がソース・ファイルにない場合は、ファイルの終わりまで適用されます。

行の追加、挿入、および変更

別のファイルにsedスクリプトを書いているのですから、追加、挿入、および変更の行コマンドを利用することができます。これらのコマンドは、現在行の後に行を挿入したり、現在行の前に行を挿入したり、パターン・スペース内で現在行を置き換えます。また複数の行を出力に挿入することもできます。挿入コマンドは次のように使います。

i\This line will be inserted before each line

このコマンドにアドレスを指定しない場合、どの行にもコマンドが適用され、次のような出力が得られます。

This line will be inserted before each line
line 1 here
This line will be inserted before each line
line 2 here
This line will be inserted before each line
line 3 here
This line will be inserted before each line
line 4 here

現在行の前に複数の行を挿入する場合、前の行の最後に円記号を付けて、追加の行を指定します。

i\
insert this line\
and this one\
and this one\
and, uh, this one too..

追加コマンドも同様な働きをしますが、パターン・スペースの現在行の後ろにだけ、行を挿入します。次のように使います。

a\
insert this line after each line.  Thanks! :)

一方、「行の変更」コマンドはパターン・スペースの現在行を実際に置き換え ます。次のように使います。

c\
You're history, original line! Muhahaha!

追加、挿入、および変更の行コマンドの指定は複数行にわたって入力する必要があるので、コマンドをテキストsedスクリプトに入力し、-fオプションでそのファイルをソースとして指定します。他の方法でsedにコマンドを渡すと問題が起きるでしょう。

次回のお知らせ

次回、sedに関するこの連載の最終回の記事では、多くの種類の異なるタスクへのsedのすばらしい実際の使用例をたくさんご覧に入れます。スクリプトが何をするかということだけではなく、なぜ そうするのかということも説明します。これを読み終わると、ご自身のいろいろなプロジェクトでのsedの使用法についての新たなすばらしいアイデアが浮かぶことでしょう。ではまたお会いしましょう。


ダウンロード可能なリソース


関連トピック


コメント

コメントを登録するにはサインインあるいは登録してください。

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Linux
ArticleID=230683
ArticleTitle=共通テーマ: 実例でわかる sed 第 2 回
publish-date=10012000