目次


共通テーマ

実例でわかる sed 第 1 回

強力な UNIX テキスト・エディターを使ってみる

Comments

コンテンツシリーズ

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

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

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

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

エディターを選ぶ

UNIX の世界では、ことファイルの編集に関しては、たくさんの選択肢があります。考えてみてください。色々あると思いますが、なかでも vi、emacs、jed などが頭に浮かんできます。誰にでも、使ってみて手放せなくなった、お気に入りのエディター (気に入ったキー割り当てに加えて) があるものです。私たちは、頼りになるエディターさえあれば、UNIX 関連のシステム回りの仕事やプログラミングなら、いくらあろうと苦もなく取り掛かる心構えはできています。

しかし、対話式エディターはすばらしいものではありますが、それにも限界があります。その対話式の特性は長所ですが、同時に短所でもあります。一連のファイルに、同じような変更を加えなければならない場合を考えてみてください。即座にお気に入りのエディターを立ち上げ、平凡で、単なる繰り返しの、そして時間のかかる山ほどの編集作業を、手作業で行っていくという方法もとれます。しかし、もっとうまいやり方があるのです。

sed を立ち上げる

もしも、ファイルの編集作業が自動化できたら、すばらしいことでしょう。その場合、ファイルの "バッチ" 編集ができ、既存のファイルへの複雑な変更を行うためのスクリプトを書くこともできるようになります。幸いにも、このような時にうまい方法があるのです。それは "sed" と呼ばれるものです。

sed は Linux を含むほとんどすべての UNIX ベースのシステムに含まれる、システムに対する負荷が軽いストリーム・エディターです。sed には、たくさんのすばらしい機能が備わっています。まず第 1 に、非常にシステム負荷が軽いということです。一般的に言って、あなたのお気に入りのスクリプト言語の数分の一の負荷です。第 2 に、sed はストリームエディターなので、パイプラインなどの stdin から受け取るデータを、編集できるということです。ですから、編集するデータをディスク上のファイルに格納する必要はありません。データは、パイプによって容易に sed へ渡せるので、強力なシェル・スクリプトの、長くて複雑なパイプラインの一部として、sed を使うことは非常に簡単です。このことを、あなたのお気に入りのエディターで可能か試してみてください。

GNU sed

さいわい Linux ユーザーには、sed の最良のバージョンである GNU sed が提供されており、現時点でそのバージョンは 3.02 です。どの Linux ディストリビューションにも GNU sed が含まれています (そのはずです)。GNU sed が広く使われているのは、そのソースが自由に配布可能であるからだけはではなく、期せずして、扱いやすく、操作に要する時間を節約してくれる数多くの拡張機能があるからです (POSIX sed 標準に対するものです)。GNU sed は、初期の所有権付であったバージョンが抱えていた多くの制限事項 (たとえば、1 行の長さの制限など) を解決しています。-- GNU sed はどんな長さの行も容易に扱えます。

最新の GNU sed

この記事の調査をしている間に、何人かのオンライン sed マニアが GNU sed 3.02a を照会したことに気付きました。不思議なことに、sed 3.02a は ftp.gnu.org (リンクは参考文献を参照) では見つからなかったので、他の場所を探さなければなりませんでした。それは alpha.gnu.org の /pub/sed の中にありました。私は手際よくそれをダウンロードし、コンパイルし、インストールし、数分の後に sed の最新バージョンが 3.02.80 であることが分かりました。alpha.gnu.org の 3.02a のソース・コードのすぐ次に 3.02.80 のコードを載せてあります。GNU sed 3.02.80 をインストールしたので、ようやく準備が整いました。

sed のバージョン

この連載では GNU sed 3.02.80 を使います。この連載の今後の記事に出てくる、最も先進的な例のいくつか (しかし極めて稀ですが) は GNU sed 3.02 あるいは 3.02a では作動しません。もし GNU 以外の sed をお使いの場合は、結果が違う可能性があります。少々お時間を割いて、GNU sed 3.02.80 をインストールすることをお勧めします。そうすれば、単に今後の連載のための準備が整うだけでなく、実在する sed で恐らく最善のものを使えるようになります。

sed の例

sed は、ユーザー指定の編集操作 (「コマンド」) を入力データに対して実行することで、機能を果たします。(「コマンド」は、いくつでも指定できます) sed は行単位の処理を基本とし、そのためコマンドは 1 行ずつ順次に実行されます。そして標準出力 (stdout) にその結果を書き込み、入力ファイルには何の変更も行いません。

いくつかの例を見てみましょう。最初の数個は少し不自然なものです。それは実用的な作業を行うためのものではなく、sed の動作を説明するためのものだからです。しかし sed を初めて勉強しようとする人にとって、それを理解することは非常に重要です。最初の例です。

$ sed -e 'd' /etc/services

このコマンドをタイプしても、出力は全く得られません。一体何が起きたのでしょうか?この例では 1 つの編集コマンド 'd' で sed をコールしました。sed は /etc/services ファイルをオープンし、パターン・バッファーに 1 行を読み込んで、編集コマンド (「行の削除」) を実行し、その後 (空の) パターン・バッファーを出力しました。次に後続の行に対してこれらのステップを繰り返しました。出力は作成されませんでした。なぜなら 'd' コマンドはパターン・バッファーのすべての単一行を消去したからです!

この例で注意すべき点がいくつかあります。第 1 に /etc/services はまったく変更されなかったということです。これは、繰り返しになりますが、sed はコマンド行で指定された、入力に使うファイルからは読み込むだけであり、そのファイルを変更しようとはしません。2 番目の注意点は sed が行指向であるということです。'd' コマンドは、入ってくるすべてのデータを、一括して瞬時に削除するように、簡単な指示を sed に出した訳ではありません。そうではなく、パターン・バッファーと呼ばれる内部のバッファーに、/etc/services の各行を一行ずつ読み込みました。1 つの行がパターン・バッファーに読み込まれると、'd' コマンドが実行され、パターン・バッファーの内容 (この例では何もない) が出力されます。後ほど、コマンドを適用する行をコントロールするためのアドレス範囲の使用法を説明しますが、アドレスの指定が無い場合は、コマンドがすべての行に適用されます。

3 番目の注意点は単一引用符を使って 'd' コマンドを囲んでいることです。sed コマンドを単一引用符で囲むことを習慣づけるのをお勧めします。こうするとシェルによる展開は行われなくなります。

sed の別の例

これは /etc/services ファイルの最初の行を出力ストリームから取り除くための sed の例です。

$ sed -e '1d' /etc/services | more

ご覧のように、このコマンドは最初の 'd' コマンドに非常によく似ていますが、'1' が前に付いています。'1' が行番号 1 を示しているとご推察なら、その通りです。最初の例では、'd' だけを使いましたが、今度はオプションの数値アドレスが前に付いた 'd' コマンドを使います。アドレスを使うことにより、特定の行に対してだけ編集を行うように指示することができます。

アドレス範囲

次に、アドレス範囲の指定方法を見てみましょう。この例では、出力の 1 行目から 10 行目までを削除します。

$ sed -e '1,10d' /etc/services | more

2 つのアドレスをコンマで分離すると、最初のアドレスから 2 番目のアドレスまでの範囲に後続のコマンドが適用されます。この例では 'd' コマンドは 1 行目から 10 行目に適用されました。他の行はすべて無視されました。

正規表現によるアドレス

いよいよ、より実用的な例です。/etc/services ファイルの内容を見たいのだが、中に含まれているコメントは見る必要がないものとします。ご存じのように、/etc/services ファイルではコメントは行の先頭に '#' 文字が付けられています。コメントを除くために、'#' で始まる行を削除します。このようにします。

$ sed -e '/^#/d' /etc/services | more

この例を実際に試してみて、どうなるか調べてください。望んだ仕事が成功裏に実行されたことが分かるでしょう。さて、何が起きたかを解明しましょう。

'/^#/d' コマンドを理解するために、まずそれを分解する必要があります。最初に 'd' を取り除きましょう。前に使ったのと同じ「行の削除」コマンドが使われています。'/^#/' が新たに付け加えられた部分で、これが新しい種類の正規表現アドレスです。正規表現アドレスは常にスラッシュで囲みます。正規表現アドレスはパターンを指定し、この特定のパターンと一致した行に対してだけ、その後に続くコマンドが適用されます。

そうです、'/^#/' は正規表現です。ところで、それは何をするのでしょうか?これは正規表現の復習をする絶好の機会です。

正規表現の復習

正規表現を使うと、テキストに出てくるパターンを表現することができます。今までにシェル・コマンド行で "*" 文字を使ったことがある人は、正規表現と非常によく似たものをすでに使用しています。正規表現では次の特殊文字を使うことができます。

文字説明
^行の先頭と一致
$行末と一致
.任意の一文字と一致
** の前の文字の n 回の繰り返しと一致、n は 0,1,2, ...
[ ][ ] 内部のすべての文字との一致

多分、正規表現を習得する最良の方法は、多くの使用例を調べることです。次の例はどれも、コマンドの左側に使われる有効なアドレス表現です。

正規表現説明
/./少なくとも文字が 1 つ含まれている行と一致する
/../少なくとも文字が 2 つ含まれている行と一致する。
/^#/'#'で始まる行と一致する
/^$/すべてがブランク行と一致する
/}$/'}' が最後にある (その後にスペースのない) 行と一致する
/} *$/'}'で終わっている行、および '}' の後にスペースだけがある行と一致する
/[abc]/小文字の 'a'、'b'、'c' のいずれかを含む行と一致する
/^[abc]/'a'、'b'、'c' のいずれかの文字で始まる行と一致する

これらの例のいくつかを試してみることをお勧めします。正規表現に慣れるために時間を割いて、自分で作成した正規表現もいくつか試してみてください。正規表現を次のように使うこともできます。

$ sed -e '/regexp/d' /path/to/my/test/file | more

これは一致した行をすべて削除します。しかしながら、"regexp" のパターンと一致した行を出力し、一致しない行を削除する方が、逆のケースよりも容易に正規表現を理解できるでしょう。これは次のコマンドで実行できます。

$ sed -n -e '/regexp/p' /path/to/my/test/file | more

新しい '-n' オプションに注意してください。これは出力することが明示的に指示されていない限り、パターン・スペースを出力しないよう指示します。また 'd' コマンドが 'p' コマンドで置き換わっていることに気付くでしょう。推察のとおり、これはパターン・スペースを出力するよう明示的に命令します。そこで、今度は一致したものだけが出力されます。

アドレスについての補足

これまで、行アドレス、行範囲アドレス、正規表現アドレスについてひと通り眺めてきました。けれどもアドレスを使ってもっといろいろなことができるのです。2 つの正規表現をコンマで区切って指定することができます。1 番目の正規表現に最初に一致した行から、2 番目の正規表現に一致した行までの、すべての行が一致します。たとえば、次のコマンドは "BEGIN" が含まれている行から、"END" が含まれている行までの、テキスト・ブロックを出力します。

$ sed -n -e '/BEGIN/,/END/p' /my/test/file | more

"BEGIN" が見つからなければデータは出力されません。"BEGIN" が見つかり、"END" が見つからなければ、以後のすべての行が出力されます。そうなるのは sed のストリーム指向の特性のためです。前の行は、後の行で "END" が現われるかどうかに関係無く、処理されます。

C 言語のソースの例

C 言語のソース・ファイルの main() 関数だけを出力する場合は、次のようにタイプします。

$ sed -n -e '/main[[:space:]]*(/,/^}/p' sourcefile.c | more

このコマンドには 2 つの正規表現、'/main[[:space:]]*(/' および '/^}/' と、1 つのコマンド 'p' が使われています。最初の正規表現は、"main" とその後に続く複数のスペースかタブ、その次に括弧 "(" が出現する文字列と一致するでしょう。これは普通の ANSI-C main() 関数の宣言と一致するはずです。

この特別な正規表現には、'[[:space:]]' 文字クラスが使われています。これはタブ (TAB) あるいはスペースのいずれかとの一致を指示する簡単な特殊キーワードです。好みにより、'[[:space:]]' とタイプする代わりに、'['、リテラル・スペース、Control-V、リテラル・タブおよび ']' とタイプすることもできます。Control-V は拡張コマンドの実行ではなく、「本当の」タブを挿入することを明示的に示しています。'[[:space:]]' コマンド・クラスを使うほうがより明確です。特にスクリプトではそうです。

さて、今度は 2 番目の正規表現です。'/^}' は新しい行の先頭に現われる '}' 文字と一致します。もしコードが正確にフォーマットされていれば、これは main() 関数の終わりの括弧に一致するはずです。フォーマットが適切でなければ、一致しないでしょう。パターン・マッチングを実行する際の微妙なことの 1 つです。

'p' コマンドの動作はいつも通り、行の出力を明示的に指示します。sed の '-n' の 非出力モードだからです。コマンドを C 言語のソース・ファイルに実行してみましょう。先頭の 'main()' と終わりの '}' を含め、main() { } ブロック全体が出力されます。

次回のお知らせ

今回は、基本的なことがらに触れましたが、次の 2 回の記事ではペースを上げましょう。もしもっと中身の濃い sed の参考文献を期待しているのなら、我慢してください - すぐ出てきます! さしあたり、次の sed と正規表現の参考文献に目を通されることをお勧めいたします。


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


関連トピック


コメント

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

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