複数ファイルの文字列置換をワンライナーで

サブフォルダを含めて複数のテキストファイル内を文字列置換したい。一括で。
フォルダ構成の想定は下記のような感じ。実際には、もっと深い階層にたくさんファイルがある。

findでファイルを抽出してsedにわたす。

以前、bashでの置換例を書きsedは遅いと書いた。
bashで文字列置換をする

遅くても便利なら使うのだ。

さて、"abc" を "efg" に書き換えるワンライナー(1行実行)の例は下記。

find . -type f -name "*.twig" -exec sed -i 's/abc/efg/g' {} +

これは、Gnu版のsedを使用する例
sed --version
sed (GNU sed) 4.2.2

同じコマンドをMacOSのBSD版で実施すると、エラーになる
エラーメッセージ「sed: 1: "./t2/index.twig": invalid command code .」
これには、ちょっとはまって混乱した。

BSD版sedでは、バックアップをとらないとき -i '' という明示をしないとだめということらしい。

Macでは、下記のコマンドになる。

find . -type f -name "*.twig" -exec sed -i '' 's/abc/efg/g' {} +

逆に、GNUのsedでこれをやると、
エラー「sed: s/abc/efg/g を読み込めません: そのようなファイルやディレクトリはありません」
-i に関して互換性がない。

ちなみに、今回一括置換をやろうと思ったのは、EC-CUBEのバージョンアップで .twig ファイルの書式が変わり、修正が必要になったため。

{% for f in form if f.vars.eccube_form_options.auto_render %}
↓
{% for f in form|filter(f => f.vars.eccube_form_options.auto_render) %}

下記のようなワンライナーで処理。

find . -type f -name "*.twig" -exec sed -i 's/ if f.vars.eccube_form_options.auto_render %}/|filter(f => f.vars.eccube_form_options.auto_render) %}/g' {} +

2026年10月20日追記。

find で複数条件を指定たとえば .htmlと.htmを抽出するとき、

find . -type f \( -name "*.html" -o -name "*.htm" \)

-oで条件をつなぎ、()でまとめるが、エスケープが必要。

また、URLを置換するような場合はsedの置換でデリミタを変えることも可能。
デリミタをスラッシュからドル記号へ変更した例

find . -type f \( -name "*.html" -o -name "*.htm" \) -exec sed -i '' 's$https://www\.example\.com/$./$g' {} +

さらに対象となるファイル、今回の場合htmlがShift-JISの場合、Macではエラーになった。
sed: RE error: illegal byte sequence

UTF-8として処理しようとして文字コードが問題なのだと思う。
LC_ALL=Cをつける。

find . -type f \( -name "*.html" -o -name "*.htm" \) -exec env LC_ALL=C sed -i '' 's$https://www\.example\.com/$./$g' {} +

ただし、上記ではsed -i ''により、置換対象ではないファイルも触られるためタイムスタンプが変わってしまう。
厳密に行うには、先に置換対象文字列を含んだファイルだけを抽出してsedにわたす方法で回避できる。

find . -type f \( -name "*.html" -o -name "*.htm" \) -exec sh -c 'for f do if LC_ALL=C grep -qF "https://www.example.com/" "$f"; then LC_ALL=C sed -i "" "s\$https://www\\.example\\.com/\$./\$g" "$f"; fi; done' sh {} +

sh -c の中で sed の置換式をダブルクォートで囲んでいるため、区切り文字の $ を \$ とエスケープしている。