at posts/single.html

複数ファイルの一括置換

複数のファイルに含まれている文字列を一括で置換したい。 たぶんありがちなネタなんだろうけど、自力でやってみることにした。

お題

拡張子が .rb の全てのファイルに対して、

#!/usr/bin/env ruby

#!/usr/local/bin/ruby

に置換する。 ついでに、「ruby」という文字を「perl」に置き換える。

とりあえずの結論

find . -name '*.rb' | xargs ruby -p -i -e '
gsub(%r{^/usr/bin/env ruby$}, "#!/usr/local/bin/ruby")
gsub(%r{ruby}, "perl")
'

大切なのは

結論じゃなくてプロセス。 どうやって答えにたどり着いたかが大切。 ってことで、メモ。

やらなきゃいけないことは、たぶん2点に分けられる。

  1. 拡張子 .rb のファイルの一覧を抽出する
  2. ファイルの内容を置換する

両方を一度にやると大変なので、順番に片付けていく。

拡張子 .rb のファイルの一覧を抽出する

ls と grep を駆使すればできるかも…と一瞬思ったけど、いつも使い方が分からない find を使ってみることにした。 find linux でググって、簡単な使い方が分かった。

$ find -name '*.rb'
./misc/style/etdiary/etdiary_test.rb
./misc/style/etdiary/etdiary_style.rb
./misc/style/wiki/wiki_parser.rb
./misc/style/wiki/wiki_style.rb
(中略)
./update.rb
./index.rb
./tdiary.rb

次に、それぞれのファイルに対してコマンドを実行する方法を調べる。

$ find -name '*.rb' | ls -l

と書いてみたけどうまく動かない(ファイルのリストがパイプでまとめて渡されるので当たり前)。 調べたら xargs というコマンドを使えばよさそう。

$ find -name '*.rb' | xargs ls -l
$ find -name '*.rb' | xargs ls -l
-rwxr-xr-x  1 machu machu  3076 Feb 24 11:26 ./index.rb
-rwxr-xr-x  1 machu machu  2626 Feb 24 11:26 ./misc/convert2.rb
(中略)
-rwxr-xr-x  1 machu machu  6246 Feb 24 11:26 ./tdiary/wiki_style.rb
-rwxr-xr-x  1 machu machu  2768 Feb 24 11:26 ./update.rb

なんかそれっぽくなった。

ファイルの内容を置換する

引数で受け取ったファイルの内容を置換するプログラム。 Ruby で書いた。

ruby -e 'ARGF.each{|line|
  line.gsub(%r{^/usr/bin/env ruby$}, "#!/usr/local/bin/ruby")
  line.gsub(%r{ruby}, "perl")
  puts line
}'

このままだと置換後の内容が標準出力に表示されるだけ。 Rubyリファレンスマニュアル - Rubyの起動を読むと、 -i オプションが使えることが分かった。

引数で指定されたファイルの内容を置き換える(in-place edit)ことを指定します。

と言うわけで、 -i オプションをつけて、さっきの find と組み合わせることで目的達成。

find -name '*.rb' | xargs ruby -i -e 'ARGF.each{|line|
  line.gsub!(%r{^/usr/bin/env ruby$}, "#!/usr/local/bin/ruby")
  line.gsub!(%r{ruby}, "perl")
  puts line
}'

あとは ARGF の代わりに -p を使ったりとかで最適化して冒頭に戻る。

関連する日記