ほとんどのDebian / Ubuntuシステムにインストールされているデフォルトのツールを使用して、ディレクトリ内の複数のファイルを再帰的に検索して置き換える方法は?
Stack*には、この質問に対する回答を見つけるためのいくつかの回答があります。ここまたはここ。しかし、これらすべては本質的に不足しています。可能な入力のいくつかの「簡単な」サブセットを除いて、正しい解決策は提供されません。
、および のマニュアルページを検索し、詳細を見た後grep
、これは私が構築することができた最高の"検索と置換"コマンドです。xargs
sed
吹く:
grep -ErlIZ -- '<OldPattern>' . | xargs -0rL1 sed -ri 's/<OldPattern>/<NewPattern>/g'
(参考までに、私は便利で高度なシェル機能を最大限に活用したいので、あまり心配しません。しかし、POSIXまたは移植性に関して - 私はまた、Macで最も古いGNUツールのバージョンについてはあまり気にしません.)
この1行には、次のようないくつかの特徴があります。
- 安全のためにバイナリを明示的に無視してください(ただし、これが本当に必要かどうかはわかりません)。
grep | xargs
候補ファイルをフィルタリングし、巨大なディレクトリで優れたパフォーマンスを提供するために使用されます。- ダッシュ()で
-
始まるパターンを許可します。 - スペースを含むパスを許可する
- 検索パターンで正規表現キャプチャグループを許可する
しかし、機能セットの欠点のため、sed
正規表現エンジンは常に貪欲そして、この動作を無効にするオプションはありません(醜い回避策のみ)。これは、少なくとも場合によっては1行に1つだけ置き換えることができることを意味します(必要に応じていくつかの例を示すことができます)。
while
必要な回数だけ実行するようにループを使用します。本物可能なすべての選択肢を扱います。
while FILES="$(grep -ErlI -- '<OldPattern>' .)"; do
echo "$FILES" | xargs -rL1 sed -ri 's/<OldPattern>/<NewPattern>/g'
done
しかし今BashはNULLバイトを格納できませんしたがって、オプションをgrep -Z
削除する必要があります。これにより、スペースを含むパスとの互換性が低下すると思います。xargs -0
while
空白のあるパスをサポートするために、ループソリューションをオプションと-Z
組み合わせる-0
ことはできますか?それとも…他の構築方法がありますが、より良い方法があるかもしれません。強いそして信頼できる検索と置換コマンドを使用しますか? (簡潔さが特徴なので、できるだけ一行に近づけておいてください)
編集するsed
:非ループ版で貪欲な正規表現が問題になる例を追加します。
次のバーを使用してください。
set(requires "gstreamer-1.5 gstreamer-base-1.5 gstreamer-sdp-1.5 libjsonrpc")
パターンは(gst.*)1\.5
次のように一致します。
set(requires "[gstreamer-1.5 gstreamer-base-1.5 gstreamer-sdp-1.5] libjsonrpc")
欲が多いので最初からgst
最後まで持ってきます1.5
。置換が次のようになると\1AAA
(\1
キャプチャグループ)が保持され、AAA
元の文字ではなくその文字のみが印刷されます1.5
。結果は次のとおりです。
set(requires "gstreamer-1.5 gstreamer-base-1.5 gstreamer-sdp-AAA libjsonrpc")
したがって、その行で可能なすべての一致を実際に置き換えるには、コマンドを合計3回実行する必要があります。ループwhile
バージョンは、検索パターンが見つからなくなるまですべてを繰り返し実行し、この時点で交換操作が完了します。実際にもう終わりました。
ベストアンサー1
成功するたびに引き続き置換を実行するには、次のように条件付きsed
ループを使用しますt
。
grep -ErlIZ -- '<OldPattern>' . |
xargs -r0 sed -Ei -e :1 -e 's/<OldPattern>/<NewPattern>/g' -e t1
sed
効率のためにファイルごとに1つずつ実行するのではなく、できるだけ多くのファイルを渡し、GNUシステムの外部よりも使いやすく一貫性がありますsed
。-E
-r
grep -E
bash
変数にNULを保存する方法はありませんが、配列を使用してファイルのリストを保存できます。
バッシュ4.4+の場合:
readarray -td '' files < <(grep -ErlIZ -- '<OldPattern>' .)
その後、次のように出力できます。
((${#files[@])) && printf '%s\0' "${files[@]}" | xargs -r0 ...
または一時ファイルを使用してください。 Linuxでは、次のことができます。
exec 3<<EOF # creates a deleted empty temp file opened on fd 3
EOF
grep -ErlIZ -- '<OldPattern>' . > /dev/fd/3 || exit
# and later:
while xargs -r0a /dev/fd/3 ...; do...
exec 3<&- # file was already deleted, closing it means its data is now
# reclaimed.
おそらく、(gst.*)1\.5
次のようにする必要があります。たとえば、(\<gst[^[:space:]]*)-1\.5\>
変数部分に空白文字が含まれておらず、一致しないことを望む場合。tagst-1.11.51
この例では、貪欲ではない演算子を使用することはおそらくあまり役に立ちません。 Perlのようなものはgst.*?1.5
まだgstreamer-1.3 foobar-1.5
一致しています。set(requires "gstreamer-1.3 foobar-1.5 gstreamer-sdp-AAA libjsonrpc")