複数のファイルの中身に対して一括置換したい場合、いちいち、ファイルをエディターで開いて置換をするのは面倒。shell スクリプトかワンライナーを書きたい。ぼくはそんな時、sed ではなく ed コマンドを使ってる。
ed のおさらい
ed は、ライン・エディターと呼ばれる種類のエディター。vi や Emacs、秀丸、gedit などは、スクリーン・エディターと呼ばれる。
ed は、- オプション (?) で標準入力からコマンドを受け取るようになる。例えば、fuga.txt 内の foo を bar に置換する場合を考えやう。次のようにする。ヒア・ドキュメントを使ってコマンドを ed に渡す:
$ ed - fuga.txt <<EOF %s/foo/bar/g w EOF
ed に渡すコマンドは、一行一コマンドで書く。
sed と違う点は二つ。sed はデフォールトで、ファイルの中身全体に置換を行なう。一方、ed は置換を行なう範囲を教えてあげないといけない。例えば、今回の例だと 1s/foo/bar/g とすると一行目に対してのみ置換が行なわれる。1,10s/foo/bar/g なら一行目から十行目まで。1,$s/foo/bar/g なら一行目からファイルの終わりまで置換が実行される。上の例で使った % は 1,$ と同じ意味を持つ。これが注意の一点目。
二点目は、ed ではファイルを保存しないといけない、ということ。だって、エディターだからね。ファイルを編集した後は、保存しないと! ファイルの保存コマンドは w。
ワンライナー で ed を使う
ワンライナーで使う場合は、echo コマンドで ed のコマンドを渡す。
$ echo "%s/foo/bar/g\nw\n" | sed - fuga.txt
シェル・スクリプトで ed を使う
シェル・スクリプトや、ed のコマンドを使い回すような具合は、ed のコマンドを別途ファイルに書き出しとくといいかな。
$ cat hoge.ed %s/foo/bar/g w $ sed - fuga.txt < hoge.txt
つまり、複数ファイルの一括置換はこんな感じ 。
$ for i in *; do ed - "$i" > hoge.txt; done
元ネタ...
このエントリーは、某日記さんのエントリーにインスパイヤーされて書いた。
複数のファイルの特定文字列を一括変換したい場合、
for i in *; do cat < "$i" | sed 's/AAA/BBB/' > "$i"; doneとすると良さそうに見えるけど、これってポータブルなのかねぇ。
某日記(中期) より引用
この sed を使った方法で
for i in *; do sed 's/AAA/BBB/' > "$i" < "$i"; done
が使えないのは、出力先を指定した時点で、その出力先のファイルが新規のファイルとしてオープンされしてしまうからだったかな? その後に「入力」がファイルを開こうとするけど、既に「出力」によって空ファイルに差し換えられてるから、置換に失敗してしまう、と。ぼくはそんな風に理解してる。合ってる?
それで、某日記さんや odz buffer さんは、sed 使ってのを解決しようと頑張ってる。
ぼくも、sed で何とか出来ないものかと思っていた時期がありました。
で... 結局、ファイルを編集したいなら、エディターを使えば早いという結論に落ちついた。
ed のポータビリティー
ed は、vi よりも基本的なコマンドだから、ほとんどの Unix に入ってると思う。FreeBSD にあるかどうか心配だけど、FreeBSD 用の ed の man が Google で見つけられたから、きっと大丈夫でせう。ただ、昔の Cygwin には vi はあっても ed がなかった。あれはショックだった。
最後に、ぼくのメモの中でこんなのを見つけた。
ed には大きなファイルを書き換えられない、という制限がある。大体 100,000 文字以上は扱えないらしい。
出典が不明なんだけど、注意しとかないといけないかも。
echo -en "s/foo/bar/g\nw\n" | ed - fuga.txt
ReplyDelete