コマンドラインから再帰的な検索と置換を行うにはどうすればよいですか?


84

bashやzshellなどのシェルを使用して、再帰的な「検索と置換」を行うにはどうすればよいですか?言い換えると、このディレクトリとそのサブディレクトリ内のすべてのファイルで、すべての「foo」を「bar」に置き換える必要があります。


同じ質問のための代替の答えはここで見つけることができますstackoverflow.com/questions/9704020/...
dunxd


これをvimで試すことをお勧めします。そうすれば、確認機能を使用して、意図しないものをスワップしないようにすることができます。ディレクトリ全体で実行できるかどうかはわかりません。
サミーベンチェリフ

回答:


106

このコマンドで実行できます(Mac OS X LionとKubuntu Linuxの両方でテスト済み)。

# Recursively find and replace in files
find . -type f -name "*.txt" -print0 | xargs -0 sed -i '' -e 's/foo/bar/g'

仕組みは次のとおりです。

  1. find . -type f -name '*.txt'現在のディレクトリ(.)以下で、-type f名前がで終わるすべての通常ファイル()を検索します.txt
  2. | そのコマンドの出力(ファイル名のリスト)を次のコマンドに渡します
  3. xargs これらのファイル名を収集し、それらを1つずつ渡します sed
  4. sed -i '' -e 's/foo/bar/g'「バックアップなしで所定の場所でファイルを編集し、次の置換(s/foo/bar)を行ごとに複数回行う(/g)」(を参照man sed

行4の「バックアップなし」の部分は私にとっては問題ないことに注意してください。変更するファイルはいずれにせよバージョン管理下にあるので、間違いがあった場合は簡単に元に戻すことができます。


9
-print0オプションなしでパイプ検索結果をxargsに決してパイプしないでください。名前にスペースなどが含まれているファイルでは、コマンドは失敗します。
-slhck

20
また、find -name '*.txt' -exec sed -i 's/foo/bar/g' {} +GNU findでこれらすべてを行うだけです。
ダニエルアンダーソン

6
私が取得sed: can't read : No such file or directory私が実行したときにfind . -name '*.md' -print0 | xargs -0 sed -i '' -e 's/ä/ä/g'、しかし、find . -name '*.md' -print0多くのファイルのリストを与えます。
マーティントーマ14年

9
これは、-iとの間のスペースを削除すると機能します''
カナダのルーク14年

3
意味は何である''の後にsed -i''の役割は?
ジャス

27
find . -type f -name "*.txt" -exec sed -i'' -e 's/foo/bar/g' {} +

これにより、xargs依存関係が削除されます。


4
これはGNU sedでは機能しないため、ほとんどのシステムで失敗します。GNU sedでは、-iとの間にスペースを入れないでください''
slhck

1
受け入れられた答えは、それを説明するのにより良い仕事をします。ただし、正しい構文を使用する場合は+1。
orodbhen

9

Gitを使用している場合、これを行うことができます。

git grep -lz foo | xargs -0 sed -i '' -e 's/foo/bar/g'

-lファイル名のみをリストします。-z各結果の後にヌルバイトを出力します。

プロジェクトの一部のファイルにはファイルの最後に改行がないため、これを行うことになり、sedは他の変更を加えない場合でも改行を追加しました。(ファイルの最後に改行が必要かどうかについてのコメントはありません。🙂)


1
このソリューションの大きな+1。残りのfind ... -print0 | xargs -0 sed ...ソリューションは、はるかに時間がかかるだけでなく、まだ存在しないファイルに改行を追加します。これは、gitリポジトリ内で作業する際の迷惑です。git grepそれに比べて速く点灯しています。
ジョシュKupershmidt 16

3

これは私がこれに使用するzsh / perl関数です。

change () {
        from=$1 
        shift
        to=$1 
        shift
        for file in $*
        do
                perl -i.bak -p -e "s{$from}{$to}g;" $file
                echo "Changing $from to $to in $file"
        done
}

そして、私はそれを使用して実行します

$ change foo bar **/*.java

(例えば)


2

試してください:

sed -i 's/foo/bar/g' $(find . -type f)

Ubuntu 12.04でテスト済み。

サブディレクトリ名やファイル名にスペースが含まれる場合、このコマンドは機能しませんが、サブディレクトリ名がある場合は機能しないため、このコマンドは使用しないでください。

通常、ディレクトリ名とファイル名にスペースを使用することは悪い習慣です。

http://linuxcommand.org/lc3_lts0020.php

「ファイル名に関する重要な事実」をご覧ください


名前にスペースが含まれるファイルがある場合に試してください。(もし何かが本当であるには余りにも良いと思われるなら、おそらくそうであると言う経験則があります。あなたが他の誰もが3½年で投稿したものよりもコンパクトなソリューションを「発見」した場合、あなたは自分に尋ねるべきですそれかもしれない。)
スコット

1

このシェルスクリプトを使用する

現在、このシェルスクリプトを使用します。これは、他の回答とWebの検索から学んだことを組み合わせたものです。私はと呼ばれるファイルの中に置かれchange、私の上のフォルダに$PATHしていましたchmod +x change

#!/bin/bash
function err_echo {
  >&2 echo "$1"
}

function usage {
  err_echo "usage:"
  err_echo '  change old new foo.txt'
  err_echo '  change old new foo.txt *.html'
  err_echo '  change old new **\*.txt'
  exit 1
}

[ $# -eq 0 ] && err_echo "No args given" && usage

old_val=$1
shift
new_val=$1
shift
files=$* # the rest of the arguments

[ -z "$old_val" ]  && err_echo "No old value given" && usage
[ -z "$new_val" ]  && err_echo "No new value given" && usage
[ -z "$files" ]    && err_echo "No filenames given" && usage

for file in $files; do
  sed -i '' -e "s/$old_val/$new_val/g" $file
done

0

私のユースケースは、私が交換したかった foo:/Drive_Letterfoo:/bar/baz/xyz 私の場合は、次のコードでそれを行うことができました。大量のファイルがあった同じディレクトリの場所にいました。

find . -name "*.library" -print0 | xargs -0 sed -i '' -e 's/foo:\/Drive_Letter:/foo:\/bar\/baz\/xyz/g'

お役に立てば幸いです。


0

次のコマンドはUbuntuおよびCentOSで正常に機能しました。ただし、OS XIではエラーが発生し続けました。

find . -name Root -exec sed -i 's/1.2.3.4\/home/foo.com\/mnt/' {} \;

sed:1: "./Root":無効なコマンドコード。

xargsを介してparamsを渡そうとしたときに、エラーなしで正常に機能しました。

find . -name Root -print0 | xargs -0 sed -i '' -e 's/1.2.3.4\/home/foo.com\/mnt/'

あなたが変更され、実際-iには、-i ''おそらくあなたが変わったという事実よりも関連性がある-exec-print0 | xargs -0。ところで、おそらくあなたは必要ありません-e
スコット

0
# Recursively find and replace in files
find . -type f -name "*.txt" -print0 | xargs -0 sed -i '' -e 's/foo/bar/g'

上記は魅力のように機能しましたが、リンクされたディレクトリでは、-Lフラグを追加する必要があります。最終バージョンは次のようになります。

# Recursively find and replace in files
find -L . -type f -name "*.txt" -print0 | xargs -0 sed -i '' -e 's/foo/bar/g'
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.