文字列をawkまたはsedで再帰的に検索/置換する方法は?


674

どのようにしてすべての出現箇所を見つけて置き換えるのですか?

subdomainA.example.com

subdomainB.example.com

/home/www/ディレクトリツリーの下のすべてのテキストファイルで再帰的に?


93
ヒント:svnチェックアウトツリーで以下を実行しないでください。マジック.svnフォルダーファイルが上書きされます。
J. Polfer

7
ああ、これはまさに私がやったことです。しかし、それは機能し、害を与えていないようです。起こり得る最悪のものは何ですか?
J.カッツウィンケル2013

5
@ J.Katzwinkel:少なくとも、チェックサムが破損し、リポジトリが破損する可能性があります。
ninjagecko 2013年

3
sedを使用するすべての人のための簡単なヒント:ファイルに末尾の改行を追加します。それらが必要ない場合は、まず何にも一致しないfind-replaceを実行し、それをgitにコミットします。次に、本物を実行します。次に、インタラクティブにリベースし、最初のものを削除します。
funroll 2014年

5
xargsにパイプする前に-path ./.git -prune -oin を使用することで、gitなどのディレクトリを結果から除外できますfind . -path ./.git -prune -o -type f -name '*matchThisText*' -print0
devinbost

回答:


849
find /home/www \( -type d -name .git -prune \) -o -type f -print0 | xargs -0 sed -i 's/subdomainA\.example\.com/subdomainB.example.com/g'

-print0find新しい行ではなく、ヌル文字で区切られた各結果を出力するように指示します。万が一、ディレクトリに名前に改行が含まれるファイルがある場合でもxargs、正しいファイル名で作業できます。

\( -type d -name .git -prune \)という名前のすべてのディレクトリを完全にスキップする式.gitです。SVNを使用する場合、または保持したい他のフォルダーがある場合は、簡単に拡張できます。より多くの名前と照合してください。これはとほぼ同じ-not -path .gitですが、ディレクトリ内のすべてのファイルをチェックするのではなく、完全にスキップするため、より効率的です。-oそれがためにどのように要求された後、-pruneに動作し、実際に。

詳細については、を参照してくださいman find


132
OSXではsed: 1: "...": invalid command code .問題が発生する可能性があります。-iオプションは拡張を期待し、's/../...'コマンドを解析するようです。解決策:拡張子 ''をのような-iオプションに渡しますsed -i '' 's/...
Robert Lujo 2013

6
注:これをディレクトリで使用し、なぜsvn st変更が表示されないのか疑問に思う場合は、.svnディレクトリのファイルも変更したためです!find . -maxdepth 1 -type f -print0 | xargs -0 sed -i 's/toreplace/replaced/g'代わりに使用してください。
ACK_stoverflow 2013

57
また、gitリポジトリにいる場合は注意してください。私はこれを明確なブランチでテストすることで賢いと思ったので、何か悪いことをした場合は元に戻すことができましたが、代わりにgitインデックスが破損しました。
Ciryon 2013年

13
これgrep -r 'hello' -l --null . | xargs -0 sed -i 's#hello#world#g'を使用して、関連のないファイルを編集しないようにします(sedはファイルのエンコードを変更する場合があります)。
caiguanhao 2015年

6
「代わりに私のgitインデックスが破損しました。」これについてあまり心配しないでくださいfind .git ... | ... 'sed -i s/(the opposite from before)/g'
。git

259

:git repoを含むフォルダーではこのコマンドを実行しないでください。.gitを変更すると、gitインデックスが破損する可能性があります。

find /home/www/ -type f -exec \
    sed -i 's/subdomainA\.example\.com/subdomainB.example.com/g' {} +

ここでの他の回答と比較すると、これはほとんどの回答よりも単純で、元の質問で求めていたperlの代わりにsedを使用しています。


50
BSD sed(Mac OS Xを含む)を使用している場合は、sedの-iオプションに明示的に空の文字列引数を指定する必要があることに注意してください。例: sed -i '' 's/original/replacement/g'
Nathan Craike 2012年

2
@JohnZwinck私の間違い、+を逃した。不思議なことに、ニキータのソリューションは私にとってより速く実行されます。
Sam

6
@AoeAoe:生成されるプロセス+の数を大幅に減らしsedます。より効率的です。
John Zwinck、2015年

4
gitリポジトリのあるフォルダーでこれを安全に行うにはどうすればよいですか?
ハトシェプスト

20
検索結果からリポジトリを除外する場合は、gitリポジトリを含むフォルダで実行しても安全ですfind . -not -path '*/\.git*' -type f ...
デールアンダーソン

210

私にとって最も簡単な方法は

grep -rl oldtext . | xargs sed -i 's/oldtext/newtext/g'

1
@Anatoly:ただ1つの質問:バイナリファイル(実行可能ファイル)を除外するにはどうすればよいですか?
user2284570 2014

3
@ user2284570 -Iまたは--binary-file=without-matchgrepフラグを使用します。
Zéychin

34
これは、のようにディレクトリを除外する必要がある場合に特に効果的です.svn。例:grep -rl oldtext . --exclude-dir=.svn | xargs sed -i 's/oldtext/newtext/g'
phyatt 2015年

11
brew install gnu-sedgsedOSX で使用して、痛みの世界を回避します。
P i

1
プロジェクトにgitバージョンが付いている場合は、代わりにこれを使用してくださいgit grep -rl oldtext . | xargs sed -i 's/oldtext/newtext/g'.gitディレクトリをf * ckするのはまったく良くありません
Paolo

61

すべてのトリックはほとんど同じですが、私はこれが好きです:

find <mydir> -type f -exec sed -i 's/<string1>/<string2>/g' {} +
  • find <mydir>:ディレクトリを検索します。

  • -type f

    ファイルのタイプ:通常のファイル

  • -exec command {} +

    この-execアクションのバリアントは、選択されたファイルに対して指定されたコマンドを実行しますが、コマンドラインは、選択された各ファイル名を末尾に追加することによって構築されます。コマンドの呼び出しの総数は、一致したファイルの数よりもはるかに少なくなります。コマンドラインは、xargsがコマンドラインをビルドするのとほぼ同じ方法でビルドされます。コマンド内では、 `{} 'のインスタンスを1つだけ使用できます。コマンドは、開始ディレクトリで実行されます。


@exec付きの@ user2284570?ツール名の代わりに実行可能ファイルへのパスを設定してみてください。
I159 2014

@ I159:いいえ:実行可能バイナリを除外します(ただし、シェルスクリプトを含みます)
user2284570 2014

8
@ I159この答えはJohn Zwinckのものと同じではありませんか?
モニカを復活させてください14年

1
@ user2284570「バイナリファイル」の概念は、完全に定義されているわけではありません。fileコマンドを使用して各ファイルのタイプを判別しようとすることもできますが、その出力の不規則な変化は少し戸惑うかもしれません。-I(別名--mime)オプションは多少役立ちます、または--mime-typeあなたはそれを持っている場合。このきちんとしたワンライナーをどのように正確にリファクタリングしてこれを行うかは、残念ながらこの小さなコメントボックスの範囲外です。ヘルプが必要な場合は、別の質問を投稿してください。(おそらく、ここにリンクを含むコメントをここに追加します。)
tripleee

1
最もきれいな答え!ありがとうメイト
ジュケロク

39
cd /home/www && find . -type f -print0 |
  xargs -0 perl -i.bak -pe 's/subdomainA\.example\.com/subdomainB.example.com/g'

2
私は好奇心旺盛です-print0xargs代わりに-execまたはの代わりに使用する理由はあり-execdirますか?
フィリップ

4
「man find」から:指定されたコマンドは、一致したファイルごとに1回実行されます。つまり、/ home / wwwに2000個のファイルがある場合、「find ... -exec ...」を実行すると、perlが2000回呼び出されます。一方「見つける... | xargs ... 'は、perlを1回または2回呼び出すだけです(ARG_MAXが約32Kで、ファイル名の平均長が20であると想定)。
ロシア語を採用

2
@Employed Russian:それがあなたが使う理由ですfind -exec command {} +-それはxargsのようなコマンドの過度の呼び出しを避けますが、個別のプロセスはありません。
John Zwinck

2
どのプラットフォームで?xargsソリューションは移植可能ですが、見つかったすべてのファイルに対してサブプロセスを呼び出さない「find ... -exec」の「マジック」呼び出しはそうではありません。
ロシア語を採用

4
@EmployedRussianは、find -exec ... {} +2006
Charles Duffy

34

私にとって覚えやすい最も簡単な解決策はhttps://stackoverflow.com/a/2113224/565525、つまり:

sed -i '' -e 's/subdomainA/subdomainB/g' $(find /home/www/ -type f)

-i ''OSXの問題を解決しますsed: 1: "...": invalid command code .

:ファイルが多すぎて処理できない場合は、が表示されArgument list too longます。回避策- 上記の使用find -execまたはxargs解決策。


4
workaroundすべての場合において好ましい構文でなければなりません。
モニカを復活させてください

1
コマンド置換の問題は、$(find...)空白やその他のシェルメタ文字が含まれるファイル名をシェルが処理する方法がないことです。これが問題ではないことがわかっている場合は、この方法で問題ありません。しかし、この問題について警告されなかったり、警告を理解していなかったりする質問が多すぎます。
tripleee 2017年

30

シルバーサーチャーag)を使用するすべての人

ag SearchString -l0 | xargs -0 sed -i 's/SearchString/Replacement/g'

agはデフォルトでgit / hg / svn file / foldersを無視するので、これはリポジトリ内で実行しても安全です。


16

エクストラとしての素敵なワンライナー。git grepを使用します。

git grep -lz 'subdomainA.example.com' | xargs -0 perl -i'' -pE "s/subdomainA.example.com/subdomainB.example.com/g"

3
(別の回答へのコメントで報告されているように).git /コンテンツを上書きするリスクがないので、gitリポジトリ内で作業する場合は良い考えです。
mahemoff 14

1
おかげで、私はそれをbash関数のrefactor() { echo "Replacing $1 by $2 in all files in this git repository." git grep -lz $1| xargs -0 perl -i'' -pE "s/$1/$2/g" }使用法として使用します。たとえば、「word」を「sword」に置き換えるrefactor word swordには、次のようにしgit diffます。
Paul Rougieux

16

ファイルを再帰的に切り詰めるには、文字列インスタンスsedを使用できgrepます。

grep -rl <oldstring> /path/to/folder | xargs sed -i s^<oldstring>^<newstring>^g

実行man grepすると、次のように定義することもできます--exlude-dir="*.git" .gitディレクトリの検索を省略したい場合フラグを他の人が丁寧に指摘したgitインデックスの問題を回避。

あなたを導く:

grep -rl --exclude-dir="*.git" <oldstring> /path/to/folder | xargs sed -i s^<oldstring>^<newstring>^g

13

これはgitリポジトリと互換性があり、少し簡単です:

Linux:

git grep -l 'original_text' | xargs sed -i 's/original_text/new_text/g'

マック:

git grep -l 'original_text' | xargs sed -i '' -e 's/original_text/new_text/g'

http://blog.jasonmeridth.com/posts/use-git-grep-to-replace-strings-in-files-in-your-git-repository/に感謝)


git-grep-zオプションを一緒に使用するほうが賢明xargs -0です。
gniourf_gniourf 2016年

git grep明らかに、gitリポジトリでのみ意味があります。一般的な置換は次のようになりますgrep -r
tripleee

@gniourf_gniourf説明できますか?
Petr Peller 2017年

2
@PetrPeller:with -zを指定git-grepすると、出力フィールドが改行ではなくnullバイトで区切られます。そして、で-0xargs入力の代わりに空白の、ヌルバイトで区切って読んで(引用符で奇妙なものをしない)だろう。したがって、ファイル名にスペース、引用符、またはその他の面白い文字が含まれている場合にコマンドを中断したくない場合、コマンドは次のとおりgit grep -z -l 'original_text' | xargs -0 sed ...です。
gniourf_gniourf 2017年

10
find /home/www/ -type f -exec perl -i.bak -pe 's/subdomainA\.example\.com/subdomainB.example.com/g' {} +

find /home/www/ -type f / home / www /(およびそのサブディレクトリ)内のすべてのファイルを一覧表示します。「-exec」フラグは、見つかった各ファイルに対して次のコマンドを実行するようにfindに指示します。

perl -i.bak -pe 's/subdomainA\.example\.com/subdomainB.example.com/g' {} +

ファイルに対して実行されるコマンドです(一度に多数)。{}ファイル名に置き換えられます。+コマンドの最後に伝えますfind、多くのファイル名の一つのコマンドを構築します。

あたり find manページ:「コマンドラインは、xargsのは、そのコマンドラインを構築することとほぼ同じ方法で構築されています。」

したがってxargs -0、またはを使用せずに、目的を達成する(およびスペースを含むファイル名を処理する)ことが可能です-print0


8

私はこれが必要だっただけで、使用可能なサンプルの速度に満足できませんでした。だから私は自分で考えました:

cd /var/www && ack-grep -l --print0 subdomainA.example.com | xargs -0 perl -i.bak -pe 's/subdomainA\.example\.com/subdomainB.example.com/g'

Ack-grepは関連ファイルを見つけるのに非常に効率的です。このコマンドは〜145 000個のファイルをそよ風に置き換えました。


いいですが、grep -ril 'subdomainA' *ほど速くはありませんgrep -Hr 'subdomainA' * | cut -d: -f1
trusktr

@ヘンノ:質問が1つだけ:バイナリファイル(実行可能ファイル)を除外するにはどうすればよいですか?
user2284570 14

ack-grepが自動的に行います。
Henno 2014

@辺野:シェルスクリプトが含まれていますか?
user2284570 2014

はい。サポートされているファイルタイプの完全なリストを次に示します。beyondgrep.com
Henno

6

ディレクトリ--exclude-dir=.svn)を除外する必要があり、ファイル名にスペースが含まれている可能性がある場合(0Byte grep -Zxargs -0

grep -rlZ oldtext . --exclude-dir=.svn | xargs -0 sed -i 's/oldtext/newtext/g'

5

grep -lr 'subdomainA.example.com' | while read file; do sed -i "s/subdomainA.example.com/subdomainB.example.com/g" "$file"; done

ほとんどの人は、何かを「読み込み中のファイル」にパイプできることを知らず、ファイル名にスペースを確保しながら、厄介な-print0引数を回避します。

echosed の前にさらにを追加すると、実際に変更する前にどのファイルが変更されるかを確認できます。


-print0便利な理由は、while read単純に処理できないケースを処理するためです。改行はUnixファイル名の有効な文字であるため、コードを完全に堅牢にするためには、そのようなファイル名にも対応する必要があります。(また、read -rいくつかの厄介なPOSIXレガシー動作を回避する必要がありますread。)
tripleee '18

また、sed一致するものがなければ何もしないので、grep本当に必要ではありません。ただし、一致するファイルが多数ある場合、またはファイルの日付スタンプを不必要に更新しない場合は、一致を含まないファイルの再書き込みを回避するのに役立ちます。
tripleee 2016

5

あなたはawkを使ってこれを以下のように解決できます、

for file in `find /home/www -type f`
do
   awk '{gsub(/subdomainA.example.com/,"subdomainB.example.com"); print $0;}' $file > ./tempFile && mv ./tempFile $file;
done

これがあなたを助けることを願っています!!!


MacOで問題なく動作します!sedosx固有の設定でもバイナリが含まれていると、すべてのベースコマンドが失敗しました。
Jankapunkt

注意してください... find返されるファイルの名前にスペースが含まれている場合、これは爆発します!より安全に使用できますwhile readstackoverflow.com/a/9612560/1938956
Soren Bjornstad

5

置換する最も簡単な方法(すべてのファイル、ディレクトリ、再帰的

find . -type f -not -path '*/\.*' -exec sed -i 's/foo/bar/g' {} +

注:一部の隠しファイルを無視する必要がある場合があります。.git、上記のコマンドを使用できます。

隠しファイルの使用を含めたい場合は、

find . -type f  -exec sed -i 's/foo/bar/g' {} +

どちらの場合も、文字列fooは新しい文字列に置き換えられますbar


4

これを試して:

sed -i 's/subdomainA/subdomainB/g' `grep -ril 'subdomainA' *`

1
こんにちは@RikHic、いいヒント-このようなことを考えていました。残念なことに、上記のフォーマットはうまくいきませんでした:)それで、私はpreタグを試してみます(機能しません)-したがって、バッククォートをエスケープします:sed -i 's/subdomainA/subdomainB/g'` grep -ril 'subdomainA' /home/www/*`-これはまだあまりよく見えませんが、コピーペーストを生き残る:)乾杯!
sdaau 2011年

4
#!/usr/local/bin/bash -x

find * /home/www -type f | while read files
do

sedtest=$(sed -n '/^/,/$/p' "${files}" | sed -n '/subdomainA/p')

    if [ "${sedtest}" ]
    then
    sed s'/subdomainA/subdomainB/'g "${files}" > "${files}".tmp
    mv "${files}".tmp "${files}"
    fi

done

4

このブログ投稿によると:

find . -type f | xargs perl -pi -e 's/oldtext/newtext/g;'

どのようにスラッシュをエスケープします/か?たとえば、IPアドレスを置き換えたい場合:xxx.xxx.xxx.xxxforxxx.xxx.xxx.xxx/folder
Pathros

/\でエスケープできます。例:find . -type f | xargs perl -pi -e 's/xxx.xxx.xxx.xxx\/folder/newtext/g;'
J.Hpour 2018年

3

またはツールとvim一緒に使用してもかまわない場合は、このリンクでユーザーGertによって提供された回答をフォローアップできます-> 大きなフォルダー階層でテキストを置換する方法は?grepfind

ここに取り引きがあります:

  • 特定のパスで置き換える文字列を再帰的にgrepし、一致するファイルの完全なパスのみを取得します。(それは$(grep 'string' 'pathname' -Rl)

  • (オプション)一元化されたディレクトリでこれらのファイルの事前バックアップを作成する場合は、これも使用できます。 cp -iv $(grep 'string' 'pathname' -Rl) 'centralized-directory-pathname'

  • その後、vim与えられたリンクで提供されているスキームと同様のスキームに従って、自由に編集/置換できます。

    • :bufdo %s#string#replacement#gc | update

2

少し古い学校ですが、これはOS Xで動作しました。

いくつかの策略があります:

.sls現在のディレクトリの下の拡張子を持つファイルのみを編集します

• 「任意の文字」として評価され.ないようにエスケープする必要がありますsed

• 通常の代わりに区切り文字,として使用されますsed/

また、これはJinjaテンプレートを編集して variableしてのパスでにimport(ただし、これはトピック外です)。

まず、sedコマンドが希望どおりに機能することを確認します(これにより、変更がstdoutに出力されるだけで、ファイルは変更されません)。

for file in $(find . -name *.sls -type f); do echo -e "\n$file: "; sed 's,foo\.bar,foo/bar/\"+baz+\"/,g' $file; done

変更の準備ができたら、必要に応じてsedコマンドを編集します。

for file in $(find . -name *.sls -type f); do echo -e "\n$file: "; sed -i '' 's,foo\.bar,foo/bar/\"+baz+\"/,g' $file; done

注意-i ''してSEDコマンド、私は(で説明したように、元のファイルのバックアップを作成したくなかったインプレース編集OS X上のsedとするか、このページではロバートLujoさんのコメントで)。

sedingの皆さん、ハッピー!


2

変更することも避けるために

  • NearlysubdomainA.example.com
  • subdomainA.example.comp.other

それでも

  • subdomainA.example.com.IsIt.good

(ドメインルートの背後にあるアイデアではおそらく良くありません)

find /home/www/ -type f -exec sed -i 's/\bsubdomainA\.example\.com\b/\1subdomainB.example.com\2/g' {} \;

2

私はトップスを使います:

find . -name '*.[c|cc|cp|cpp|m|mm|h]' -print0 |  xargs -0 tops -verbose  replace "verify_noerr(<b args>)" with "__Verify_noErr(<args>)" \
replace "check(<b args>)" with "__Check(<args>)" 

`'*。[c | cc | cp | cpp | m | mm | h]'`のプラス1
FractalSpace

2

ほとんどのバージョンよりも一般的なバージョンです。たとえば、finddu代わりに使用して)必要はありません。xargsPlan9の一部のバージョン(9frontなど)にのみ存在するが必要です。

 du -a | awk -F' '  '{ print $2 }' | xargs sed -i -e 's/subdomainA\.example\.com/subdomainB.example.com/g'

ファイル拡張子のようなフィルターを追加したい場合は、以下を使用しますgrep

 du -a | grep "\.scala$" | awk -F' '  '{ print $2 }' | xargs sed -i -e 's/subdomainA\.example\.com/subdomainB.example.com/g'

1

IBMi上のQshell(qsh)の場合、OPによってタグ付けされたbashではありません。

qshコマンドの制限:

  • findに-print0オプションがありません
  • xargsには-0オプションがありません
  • sedには-iオプションがありません

したがって、qshのソリューション:

    PATH='your/path/here'
    SEARCH=\'subdomainA.example.com\'
    REPLACE=\'subdomainB.example.com\'

    for file in $( find ${PATH} -P -type f ); do

            TEMP_FILE=${file}.${RANDOM}.temp_file

            if [ ! -e ${TEMP_FILE} ]; then
                    touch -C 819 ${TEMP_FILE}

                    sed -e 's/'$SEARCH'/'$REPLACE'/g' \
                    < ${file} > ${TEMP_FILE}

                    mv ${TEMP_FILE} ${file}
            fi
    done

警告:

  • ソリューションはエラー処理を除外します
  • OPのタグが付いたバッシュではない

これには、引用符とでの行の読み取りに関する厄介な問題がありforます。
tripleee 2016

1

SVNリポジトリを完全に破壊せずにこれを使用したい場合は、次のようにして、すべての隠しファイルを無視するように「find」に指示できます。

find . \( ! -regex '.*/\..*' \) -type f -print0 | xargs -0 sed -i 's/subdomainA.example.com/subdomainB.example.com/g'

括弧は不必要に見えます。これには以前はフォーマットエラーがあり、使用できませんでした(Markdownレンダリングは正規表現の一部の文字を消費します)。
tripleee 2016

1

組み合わせを使用grepし、sed

for pp in $(grep -Rl looking_for_string)
do
    sed -i 's/looking_for_string/something_other/g' "${pp}"
done

@tripleee少し修正しました。この場合grep -Rl pattern、パターンが存在するファイルのコマンド生成リストの出力。ファイルはforループで読み込まれません。
Pawel

えっ?まだforループがあります。返されたファイル名に空白が含まれていると、シェルがfor引数リストをトークン化するため、ファイル名は正しく機能しません。しかし、ループ内で引用符なしのファイル名変数を使用するため、これを修正すると、代わりにそこで壊れます。これらの残りのバグを修正すると、@ MadMan2064の回答と同じになります。
tripleee 2016

@tripleeeはい、それは本当です、私はこれを逃しました。
Pawel

1

gitリポジトリ内のすべてのオカレンスを置き換えるには、以下を使用できます。

git ls-files -z | xargs -0 sed -i 's/subdomainA\.example\.com/subdomainB.example.com/g'

ローカルgitリポジトリのリストファイルを参照してくださいリポジトリ内のすべてのファイルを一覧表示する他のオプション。-zオプションには、そのことを保証ゼロバイト、とファイル名を分離するためにgitを伝えますxargs(オプションで-0、彼らはスペースやその他もろもろが含まれている場合でも、ファイル名を分離することができます)。


1
perl -p -i -e 's/oldthing/new_thingy/g' `grep -ril oldthing *`

1
awk/ は使用しませんsedが、perlが一般的です(busyboxのみが組み込まれた/システムを除く)。
pevik 2018年

1

複数のファイルを変更する(そしてバックアップをとして保存する*.bak):

perl -p -i -e "s/\|/x/g" *

ディレクトリ内のすべてのファイルを取得し|、「Perl pie」と呼ばれるxに置き換えます(パイとして簡単)


ただし、ディレクトリを介して再帰的ではありません。
PKHunter 2015

それにパイプすることが可能であり、これにより、ディレクトリを含めて非常に調整可能になります。josephscott.org/archives/2005/08/...unix.stackexchange.com/questions/101415/...
Stenemo
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.