web-dev-qa-db-ja.com

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

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

72
Nathan Long

このコマンドはそれを行います(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はそれらのファイル名を集めてそれらを一つずつsedに渡します
  4. sed -i '' -e 's/foo/bar/g'は、「バックアップをせずにその場でファイルを編集し、1行に複数回(s/foo/bar)を代入する(/g)」という意味です(man sedを参照)

変更しているファイルはとにかくバージョン管理下にあるので、4行目の「バックアップなし」の部分は私には問題ありません。

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

これはxargs依存関係を取り除きます。

24
Ztyx

もしあなたがGitを使っているならば、あなたはこれを行うことができます:

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

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

プロジェクトの中のいくつかのファイルはファイルの終わりに改行を持っておらず、sedは他の変更をしていなくても改行を追加したので、私はこれをやめました。 (ファイルの最後に改行を入れるべきかどうかについてのコメントはありません。)

8
David Winiecki

これは私がこれに使用する私の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

(例えば)

3
Brian Agnew

試してください:

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

Ubuntu 12.04でテスト済み。

サブディレクトリ名やファイル名にスペースが含まれていると、このコマンドは機能しません。

ディレクトリ名とファイル名にスペースを使用するのは一般的に悪い習慣です。

http://linuxcommand.org/lc3_lts0020.php

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

2
nexayq

このシェルスクリプトを使う

私は今、このシェルスクリプトを使用しています。これは、他の回答とWebの検索から学んだことを組み合わせたものです。私はそれを私の$PATHのフォルダーの中のchangeと呼ばれるファイルに入れ、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
1
Nathan Long

私のユースケースは、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
neo7

次のコマンドはUbuntuとCentOSではうまくいきました。しかし、OS Xの下で私はエラーを受け続けました:

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

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

私はxargsを介してパラメータを渡してみたとき、それはエラーなしでうまくいきました:

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