sedにおけるHello Worldの特殊事情
第8回は、sedを使ったHello Worldです。sedは本来、標準入力(または引数指定のファイル)から1行ずつ読み込み、正規表現による文字列の置換などの処理を施して標準出力に出力するという、ストリームエディタです。したがって、そもそもsedはHello Worldのプログラムにはあまり馴染みません。また、sedにはawkでいうところのBEGIN{}に相当する部分がなく、入力データがまったくない状態では処理が開始されません。このため、sedでHello Worldのプログラムを作るには、何らかのダミーデータの入力が必要になります。
そこで、まずは「#!/bin/sed」で始まるsedスクリプトを記述するのではなく、「#!/bin/sh」で始まる普通のシェルスクリプトを記述し、そのシェルスクリプトの中からsedを起動する方式でHello Worldを考えてみましょう。sedの標準入力には、ダミーデータとして、echoコマンドによる改行1個(空行)をパイプ経由で入力することにします。つまり、sedは1行の空行を入力として、それを処理する形で動作するわけです。
リスト1は、この方式によるsedのHello Worldです。sコマンドを使い、入力行(空行)すべてをHello Worldという文字列に置換しています。
リスト1 sコマンドで置換する方法(sed_command)
#!/bin/sh ← 普通のシェルスクリプトとして起動
echo | sed 's/.*/Hello World/' ← sコマンドで、1行すべてをHello Worldに置換
このスクリプトの実行方法は、普通のシェルスクリプトと同じです。実行例1のようにchmodコマンドで実行属性を付ければ実行できます。
実行例1 sedを含むシェルスクリプトの実行
$ chmod +x sed_command ← ファイルに実行属性を付ける
$ ./sed_command ← sedを含むシェルスクリプトを実行
Hello World ← 確かにHello Worldが表示される
$ ← シェルのプロンプトに戻る
sコマンド以外の方法
1行をまるごと置換する場合、cコマンドを使えば正規表現が不要になります(リスト2)。cコマンドでは、「\改行」でいったん改行して置換文字列を記述します。これをシェル上のコマンドとして実行するため、改行を含めた複数行全体をシングルクォートで囲みます。
リスト2 cコマンドで行ごと置換(sed_command2)
#!/bin/sh
echo | sed 'c\ ← cコマンドで行(空行)全体を置換(\で一旦改行)
Hello World ← 置換文字列を記述
' ← シェルのシングルクォートを閉じる
行の前に文字列を挿入するiコマンドも使えます(リスト3)。ただし、iコマンドの場合はもとの入力行(ここでは空行)も残って出力されてしまうため、sedに「-n」オプションを付けてデフォルト出力を抑制します。
リスト3 iコマンドで行を挿入(sed_command3)
#!/bin/sh
echo | sed -n 'i\ ← iコマンドで対象行の前に行を挿入(sedには-nオプション)
Hello World ← 挿入文字列を記述
' ← シェルのシングルクォートを閉じる
同様に、行の後ろに行を追加するaコマンドも使えます(リスト4)。同じく、sedには「-n」オプションが必要です。
リスト4 aコマンドで行を追加(sed_command4)
#!/bin/sh
echo | sed -n 'a\ ← aコマンドで対象行の後ろに行を追加(sedには-nオプション)
Hello World ← 追加文字列を記述
' ← シェルのシングルクォートを閉じる
あくまで「sedスクリプト」にする方法
スクリプトの1行目に「#!/bin/sed」と記述するsedスクリプトにおいて、1行目のsedの引数の部分に直接プログラムを記述すれば、スクリプトファイル自体はsedのプログラムではなく、sedへの入力ファイルとなります。この方式で、あくまで「sedスクリプト」としてHello Worldを記述することができます。
ただし、OSによる挙動の違いを回避するため、sedの引数は1つだけで、かつ、引数の途中にスペースを入れないようにする必要があります。このため、「sed -n」のようなオプションは付けられません。
リスト5は、入力ファイル(スクリプト自身)の最終行のみを出力するsedスクリプトです。「$」は最終行を表します。ここでは「-n」オプションの代わりに、プログラム中に「d」コマンドを付けて、sedのデフォルト出力を削除しています。ファイルの最終行には、メッセージを直接記述しておきます。
リスト5 最終行のみ出力する方法(sed_line)
#!/bin/sed $p;d ← 最終行のみ出力後、入力行をすべて削除するプログラム
Hello World ← メッセージを直接記述
同じ発想で、入力ファイルの最終行以外を削除するようにしたのがリスト6です。「!」は条件反転を意味します。この方法ではもともと「-n」オプションの問題はありません。
リスト6 最終行以外を削除する方法(sed_line2)
#!/bin/sed $!d ← 最終行以外の行を削除するプログラム
Hello World ← メッセージを直接記述
正規表現を使ってメッセージを抜き出すこともできます。リスト7は、「#」以外の文字で始まる空行以外の行を抜き出すという動作をします。「-n」オプションの代わりにdコマンドを付加します。
リスト7 正規表現でメッセージを抜き出す(sed_regex)
#!/bin/sed /^[^#]/p;d ← 「#」以外の文字で始まる行(空行を除く)を出力
Hello World ← メッセージを直接記述
正規表現の後ろに「!」を記述して条件を反転し、メッセージ以外の行を削除するという方法も可能です(リスト8)。
リスト8 正規表現でメッセージ以外を削除(sed_regex2)
#!/bin/sed /^[^#]/!d ← 「『#』以外の文字で始まる行(空行を除く)」以外を削除
Hello World ← メッセージを直接記述
最後は最も凝ったスクリプトで、「#!」の行自体をHello Worldに置換する1行だけのsedスクリプトです(リスト9)。この1行は、sedのプログラムであると同時に、sedへの入力データとなります。
本来ならば「#!/bin/sed s/.*/Hello World/」と書けばよいところですが、HelloとWroldの間のスペースが問題です「#!」の行のsedプログラム中にはスペースは入れられません。また、スペースを\x20などのエスケープ文字で表現する方法は、sedのバージョンによっては使えません。そこで、/bin/sedとその右側のsコマンドの間にあるスペースを利用し、「#!/bin/sed」を「Hello」に置換し、右側のsedプログラム自体を「World」に置換するようにします。ここでは、sedプログラムの中にある正規表現の/.*sed/に、それ自身がマッチしないように、/.*se[d]/と記述する必要があります。
リスト9 1行だけの凝ったsedスクリプト(sed_subst)
#!/bin/sed s/.*se[d]/Hello/;s/s.*/World/ ← 自分自身を置換する1行プログラム