UNIX/Linux及びシェルにおいてテキスト編集はメインとも言えるほど一般的であり、必須という中でテキスト内の検索文字列の置換や削除を行いたいシーンは多々あり、しかも正規表現が利用できるとかなり便利ですが、そんな時に重宝するコマンドがStream EDitorの略、sedです。
sedは、コマンドとしてコマンドラインからワンライナーで、または単独のスクリプトとして、またはコマンドラインからsedコマンドが書き連ねられたファイルを呼び出して実行することができます。
$ sed 'edit_cmd ' foo
$ sed "edit_cmd " foo
sedの引数となる編集コマンドはシェルに特殊文字を不要に解釈されることを防ぐ意味でもシングルクォートで括ることは有用で多用される一方、意外にも?変数展開を要する場合などに必須のダブルクォートも利用できます。
$ sed edit_cmd foo
クォートは省略することもでき、特に変数展開を要するシーンにおいてシングルクォートでは変数展開できず、ダブルクォートではエラーとなるなど、むしろ省略した方がよい場合もあります。
$ sed -e 'edit_cmd ' foo
$ sed -e 'edit_cmd ' -e "edit_cmd " foo
このようにsedコマンドのみから成るワンライナーをコマンドラインから実行する場合には、sed、スペース、シングルクォートまたはダブルクォートで括られた検索、削除、または検索置換などの編集コマンド、それに続けて対象ファイル名(foo)を指定し、複数のsedコマンドをワンライナーで実行したい場合(単一でもOK)、次に続く文字列が対象となる編集コマンドであることを示す-eオプションを付加します(単一でも複数でも-eオプションを付加するとエラーがあった場合でもエラーメッセージを出力しません)。
$ cat bar
sed is stream editor.
sed is good command.
$ sed '1p' foo
sed is stream editor.
sed is stream editor.
sed is good command.
この例はファイルbarの「1」行めを出力(「p」rint)する指示ですが、行操作に対するsedの既定の挙動ではマッチした行に加えて、元のファイル内容全てを標準出力へ出力するので、例でも1行めを出力した直後に全行改めて出力しているので3行出力されています。
$ sed -n '1p' foo
sed is stream editor.
-nオプションは、この例のようにpコマンドと併用すると該当行だけ表示することが可能になります。
$ cat foo
sed is stream editor.
$ sed ' s/sed/SED/ ' foo
SED is stream editor.
$ cat foo
sed is stream editor.
尚、sedの編集はsedが用意したバッファ領域に入力ファイルがコピーされ、そこで編集が行われるので、いかなる編集内容も引数となる入力ファイルには全く影響しませんから、自由に思う存分試してみることができます。
編集内容を保存したい場合は、ファイルリダイレクトや後述のsed固有の出力方法で行います。
$ cat foo
sed is stream editor.
$ sed 's/sed/SED/' foo ; sed 's/is/IS/' foo
sed IS stream editor.
よってbashなどシェルでは複数コマンドをセミコロンで連結し、一行で記述することもできるわけですが、sedに関してはリダイレクトや後述のsed固有のファイル出力を利用するか、グループ化演算子を利用しない限り、コマンドセパレータであるセミコロンで連結されただけの複数のsedコマンドは最後に編集された結果だけが反映されるので意味がありません。
$ sed -e 's/sed/SED/ ' -e "s/is/IS/" foo
SED IS stream editor.
$ sed 's/stream/Stream/' foo | sed 's/editor/Editor/'
sed is Stream Editor.
一方、sedなら前述のように-eオプション付きで複数コマンドを記述することもできますし、また、パイプを使ってsedコマンド同士を連結することで継続して編集する方法もあります。
$ find . -readable | xargs grep "sed" | \
sed 's/[eE]d..r/command/' | awk ' BEGIN { \
print "-----\nfind,grep,sed,awk command sample" } \
{ if( $4 != "editor" ) print $0 } \
END { print "good job? bad job?\n-----" } '
-----
find,grep,sed,awk command sample
sed is stream command.
good job? bad job?
-----
$ cat foo
sed is stream editor.
また、sedをfind、grep、awkなどと連結すれば、アイデア次第であらゆる編集が可能になることは言うまでもありません。
言うまでもないので書いてみたものの好例となっていないというのは内緒ですが、ただ、bash 組み込みコマンドに履歴編集用のfcコマンドがあるにはあるものの、この例のように改行を何度も要するほど、あまりに長い場合、タイプミスを回避する為にもコマンドラインからワンライナーで入力するよりも(それが本当に必要なパイプラインなら)シェルスクリプトとしてファイルに記述する方が得策でしょう。
ちなみに、ここでawk script部分でバックスラッシュ\を使って本来1行で書くところを複数行にまたいで記述していますが、改行する位置は要注意で例えばshell scriptなどでsedを記述する際にバックスラッシュ\を使ってコマンドの途中改行をしようという場合には、まさかそんなことが原因とは思いもよらず原因を特定できずに何日も悩むことにならないよう仮にもコマンドセパレータ含むコマンド内での改行は絶対に避けましょう。
scriptと言えば一般に1行めに実行ファイルパスを伴ってファイルに記述したものですが、sedには、コマンドラインからコマンドライン引数として読み込んだ「インタプリタ実行ファイルパスのない」ファイルに記述されたコマンド(群)を実行する方法と他の多くのscript同様、1行めにインタプリタパス指定をしてパーミッション設定で実行ビットを立ててscriptとして実行する方法があります。
単にコマンドを羅列した引数として渡すファイル(例/cmd_sed.txt)には実行ファイルパスも実行権限も不要ですが、コマンド以外の行には#シャープによるコメントアウトが必要、単独で実行可能なスクリプト(例/exe_script.sed)とする場合には、他のインタプリタ同様、1行めにシャープ # 、エクスクラメーションマーク(感嘆符) ! に続けて実行ファイルパス(#!/usr/bin/sed)を記述します(1行めの実行パスにsedと記述しているのでperl/python/ruby等々同様、各行に"sed"コマンドは不要)。
$ cat abc.txt
そしてここにまるでサンプルとして用意したかのような(もちろんそうなんですが)awk/find/grep/sedを簡略に説明したかのようなabc.txtというテキストファイルがあるものとします。
$ cat cmd_sed.txt
$ sed -f cmd_sed.txt abc.txt
コマンドリストとしてファイルを読み込んでコマンドとして実行する場合には、コマンドラインから-fオプション付きsedコマンドに単にsedコマンドを記述しただけのファイル(cmd_sed.txt)と入力ファイル(abc.txt)を引数として渡すと標準出力に結果が出力されます。
この例ではキーワード"sed"のある行の後ろの行にコマンドが書かれたファイルで指定されている一文を追加(addition/add/append)します。
$ cat exe_script.sed
$ chmod u+x exe_script.sed
$ ./exe_script.sed abc.txt
スクリプトファイルとして実行する場合には、インタプリタパスを記述、実行権限を許可して実行すると標準出力に出力されます。
この例ではキーワード"sed"のある行の前の行にscript内に書かれている一文を挿入(insert)します。
パイプを介して渡したり、リダイレクトで入力することもできます。
sedは、正規表現・パターンを利用した検索、検索文字列の削除、置換を伴う検索を行うことができ、対象文字列はスラッシュ/などの区切り文字・区切り記号で区切って指定、行指定の場合は数値をそのまま指定します。
pコマンドは当該文字列のある行、または指定行、またはその指定範囲を表示、dコマンドは結果を削除、wコマンドはファイル名を引数に取り結果をファイルに書き出します。
$ sed -n '/search_word/p' foo
$ sed -n '2p' foo
$ sed '/search_word/d' foo
$ sed '/start/,/end/'d foo
$ sed '3d' foo
1つめのpのコマンドは当該文字列の検索表示、2つめは行検索表示です。
2つのdの内、最初は当該文字列の削除、2つめは範囲削除、3つめは指定行削除で既定では結果は削除対象以外を標準出力に出力します。
dコマンドで-nオプションを付加してしまうと結果が一切表示されなくなってしまいますが、pとdは、ほぼ同様に利用でき、またpもdも範囲指定ではクォートの外に記述し、それ以外のケースは内側、または外側いずれも可です。
$ sed '/search_word/w bar.txt' foo
$ sed '/search_word/r hoge.txt' foo
wコマンドはクォート内に記述したファイルを引数として結果を書き出し、この場合、foo内にsearch_wordがあれば、bar.txtに(あれば上書き、なければ新規作成されて)書き出され、rコマンドは同じくクォート内に記述したファイルを引数としてファイルを読み込みfoo内のsearch_wordがある行の後ろの行にhoge.txtの内容を再帰的に追加します。
$ sed 'y/before/after/' foo
$ sed 's/before/after/' foo
sedで文字の検索置換を行うにはyコマンド、文字列の検索置換を行う場合には、sコマンドを利用し、このように記述することもできます。
sedのセパレータにはスラッシュ / が多用されますし、一部の編集コマンドではスラッシュ / でなければエラーになるものもありますが、sコマンドを中心にスラッシュ / 以外のセパレータを利用できるケースは多々あります。
$ sed 'y ! before ! after ! ' foo
$ sed 's # before # after # ' foo
これは、とても便利で例えばsedを利用しているとスラッシュ / を含んだ文字列を検索、削除、置換対象にするケースが多々あり、その際にはsedコマンドのセパレータと区別する為にバックスラッシュ \ でエスケープしても同じことはできますが、sedコマンドのセパレータにスラッシュ以外のものを使って干渉を回避すれば、勘違いやミスも起こりやすいエスケープをする必要がなくなるので楽です。
コロン : も利用できますが、例えばURLを含む場合は、スラッシュもコロンも編集対象文字列に含まれる、そういった場合にも ! 、 | 、 # 、もっと言えば、干渉さえしなければ数字や文字をセパレータにすることさえできます。
$ cat foo
sed is good command.
$ sed 's # good # very very & # ' foo
sed is very very good command.
また、検索置換を行う場合、マッチしたパターンを置換後のパターンとして利用したいケースは頻繁にあり、sedではアンパサンド(アンド記号)&にマッチしたパターンが格納されるようになっています。
更にsedでも正規表現も利用できますし、&以外にバックスラッシュと数値1文字から成る\1~\9によって丸カッコ()で括られた順に置換前パターン参照(リファレンス)を利用することができます。
$ echo abc 123 | sed -e 's/\([a-z]*\) \([0-9]*\)/\2 \1/'
123 abc
但し、sedコマンドにおいて置換前パターンを参照する場合には、パターン用の丸カッコをエスケープする必要があるので\(...\)のようにカッコの開始と終了それぞれをエスケープします。
*ちなみにPerlが参考にしたsedの機能であるsコマンドでは、$`、$&、$'などマッチしたパターンの前、マッチしたパターン、マッチしたパターンの後といったパターンを保持する特殊変数があり、置換前パターン参照には\1~\9の他$1~$9も利用可というよりむしろ後者の方が多用され、また置換前パターン参照用の丸カッコのエスケープは不要です。
ここでは、使用頻度が高く、それ相応の機能をすぐに利用できるようなコマンドとオプションに限定しましたが、これら以外についてはコマンドラインからsed、またはsed --help、更にmanやinfoで確認してください。