web-dev-qa-db-ja.com

ファイル内の文字列を置き換えるにはどうすればよいですか?

特定の検索基準に基づいてファイル内の文字列を置き換えることは、非常に一般的なタスクです。どうやって

  • 現在のディレクトリのすべてのファイルで文字列foobarに置き換えますか?
  • サブディレクトリに対して同じことを再帰的に行いますか?
  • ファイル名が別の文字列と一致する場合にのみ置き換えますか?
  • 文字列が特定のコンテキストで見つかった場合にのみ置き換えますか?
  • 文字列が特定の行番号にある場合は置き換えますか?
  • 複数の文字列を同じ置換で置き換える
  • 複数の文字列を異なる置換で置き換える
791
terdon

1.現在のディレクトリのすべてのファイルで、ある文字列のすべての出現箇所を別の文字列で置き換えます。

これらは、知っているディレクトリに通常のファイルのみが含まれており、非表示ではないすべてのファイルを処理したい場合です。そうでない場合は、2の方法を使用してください。

この回答のすべてのsedソリューションは、GNU sedを想定しています。FreeBSDまたはOS/Xを使用している場合は、_-i_を_-i ''_に置き換えます。また、sedの任意のバージョンで_-i_スイッチを使用すると、特定のファイルシステム セキュリティへの影響 が含まれ、配布を計画しているスクリプトでは推奨されないことに注意してください。

  • 非再帰的、このディレクトリ内のファイルのみ:

    _sed -i -- 's/foo/bar/g' *
    Perl -i -pe 's/foo/bar/g' ./* 
    _

    Perl 1つは、ファイル名が_|_またはスペースで終わる場合に失敗します) )。

  • このサブディレクトリとすべてのサブディレクトリにある再帰的な通常のファイル(隠しファイルを含む

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

    Zshを使用している場合:

    _sed -i -- 's/foo/bar/g' **/*(D.)
    _

    (リストが大きすぎると失敗する可能性があります。回避するにはzargsを参照してください)。

    Bashは通常のファイルを直接チェックできません。ループが必要です(中括弧はオプションをグローバルに設定しないでください):

    _( shopt -s globstar dotglob;
        for file in **; do
            if [[ -f $file ]] && [[ -w $file ]]; then
                sed -i -- 's/foo/bar/g' "$file"
            fi
        done
    )
    _

    ファイルは、実際のファイル(-f)であり、書き込み可能(-w)である場合に選択されます。

2.ファイル名が別の文字列と一致する場合/特定の拡張子を持つ場合/特定のタイプである場合のみ置換します。

  • 非再帰的、このディレクトリ内のファイルのみ:

    _sed -i -- 's/foo/bar/g' *baz*    ## all files whose name contains baz
    sed -i -- 's/foo/bar/g' *.baz    ## files ending in .baz
    _
  • このサブディレクトリとすべてのサブディレクトリにある再帰的な通常のファイル

    _find . -type f -name "*baz*" -exec sed -i 's/foo/bar/g' {} +
    _

    Bashを使用している場合(中括弧はオプションをグローバルに設定しないでください):

    _( shopt -s globstar dotglob
        sed -i -- 's/foo/bar/g' **baz*
        sed -i -- 's/foo/bar/g' **.baz
    )
    _

    Zshを使用している場合:

    _sed -i -- 's/foo/bar/g' **/*baz*(D.)
    sed -i -- 's/foo/bar/g' **/*.baz(D.)
    _

    _--_は、sedにコマンドラインでフラグが指定されなくなることを通知します。これは、_-_で始まるファイル名から保護するのに役立ちます。

  • ファイルが特定のタイプ、たとえば実行可能ファイルの場合(その他のオプションについては_man find_を参照):

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

    zsh

    _sed -i -- 's/foo/bar/g' **/*(D*)
    _

3.特定のコンテキストで文字列が見つかった場合のみ置換する

  • 同じ行に後でfooがある場合にのみ、barbazに置き換えます。

    _sed -i 's/foo\(.*baz\)/bar\1/' file
    _

    sedでは、\( \)を使用すると、括弧内にあるものがすべて保存され、_\1_でアクセスできます。このテーマにはさまざまなバリエーションがあり、そのような正規表現の詳細については、 ここ を参照してください。

  • 入力ファイルの3d列(フィールド)にfooが見つかった場合にのみ、barfooで置き換えます(空白で区切られたフィールドを想定):

    _gawk -i inplace '{gsub(/foo/,"baz",$3); print}' file
    _

    gawk 4.1.0以降が必要です)。

  • 別のフィールドの場合、_$N_を使用します。ここで、Nは対象のフィールドの番号です。別のフィールド区切り文字(この例では_:_)を使用するには、次のようにします。

    _gawk -i inplace -F':' '{gsub(/foo/,"baz",$3);print}' file
    _

    Perlを使用した別のソリューション:

    _Perl -i -ane '$F[2]=~s/foo/baz/g; $" = " "; print "@F\n"' foo 
    _

    注:awkPerlの両方のソリューションは、ファイル内のスペースに影響します(先頭と末尾の空白を削除し、一致する行の空白のシーケンスを1つの空白文字に変換します)。別のフィールドの場合、_$F[N-1]_を使用します。ここで、Nは必要なフィールド番号であり、別のフィールドセパレーターの使用(_$"=":"_は、出力フィールドセパレーターを_:_に設定します) :

    _Perl -i -F':' -ane '$F[2]=~s/foo/baz/g; $"=":";print "@F"' foo 
    _
  • foobarに置き換えるのは、4行目のみです。

    _sed -i '4s/foo/bar/g' file
    gawk -i inplace 'NR==4{gsub(/foo/,"baz")};1' file
    Perl -i -pe 's/foo/bar/g if $.==4' file
    _

4.複数の置換操作:異なる文字列で置換する

  • sedコマンドを組み合わせることができます:

    _sed -i 's/foo/bar/g; s/baz/zab/g; s/Alice/Joan/g' file
    _

    順序が重要であることに注意してください(_sed 's/foo/bar/g; s/bar/baz/g'_はfoobazに置き換えます)。

  • またはPerlコマンド

    _Perl -i -pe 's/foo/bar/g; s/baz/zab/g; s/Alice/Joan/g' file
    _
  • 多数のパターンがある場合、パターンとその置換をsedスクリプトファイルに保存する方が簡単です。

    _#! /usr/bin/sed -f
    s/foo/bar/g
    s/baz/zab/g
    _
  • または、上記のパターンペアを実行するには多すぎる場合、ファイルからパターンペアを読み取ることができます(2つのスペースで区切られたパターン、$ patternと$ replacement、1行あたり)。

    _while read -r pattern replacement; do   
        sed -i "s/$pattern/$replacement/" file
    done < patterns.txt
    _
  • パターンの長いリストと大きなデータファイルの場合は非常に遅くなるため、パターンを読み取り、それらからsedスクリプトを作成することをお勧めします。以下は、<space>区切り文字が、ファイル内で1行に1つずつ発生するMATCH <space> REPLACEペアのリストを区切ると想定しています_patterns.txt_

    _sed 's| *\([^ ]*\) *\([^ ]*\).*|s/\1/\2/g|' <patterns.txt |
    sed -f- ./editfile >outfile
    _

    上記の形式はほとんど任意であり、たとえば[〜#〜] match [〜#〜]または-のいずれかで<space>を許可しません[〜#〜] replace [〜#〜]。ただし、この方法は非常に一般的です。基本的に、sedスクリプトのように見える出力ストリームを作成できる場合は、sedを指定して、そのストリームをsedスクリプトとしてソースできます。 _のスクリプトファイルは_-_ stdinです。

  • 同様の方法で、複数のスクリプトを組み合わせて連結することができます。

    _SOME_PIPELINE |
    sed -e'#some expression script'  \
        -f./script_file -f-          \
        -e'#more inline expressions' \
    ./actual_edit_file >./outfile
    _

    POSIX sedは、コマンドラインに表示される順序ですべてのスクリプトを1つに連結します。これらのどれも_\n_ ewlineで終わる必要はありません。

  • grepも同じように機能します。

    _sed -e'#generate a pattern list' <in |
    grep -f- ./grepped_file
    _
  • 固定文字列をパターンとして使用する場合は、正規表現metacharactersをエスケープすることをお勧めします。あなたはこれをかなり簡単に行うことができます:

    _sed 's/[]$&^*\./[]/\\&/g
         s| *\([^ ]*\) *\([^ ]*\).*|s/\1/\2/g|
    ' <patterns.txt |
    sed -f- ./editfile >outfile
    _

5.複数の置換操作:複数のパターンを同じ文字列で置換します

  • foobarまたはbazのいずれかをfoobarに置き換えます

    _sed -Ei 's/foo|bar|baz/foobar/g' file
    _
  • または

    _Perl -i -pe 's/foo|bar|baz/foobar/g' file
    _
1061
terdon

良い r e pl acement Linuxツールはrplで、もともとはDebianプロジェクトなので、Debian派生ディストリビューションではapt-get install rplで利用でき、他のディストリビューションでも利用できますが、それ以外の場合はtar.gzファイルを SourgeForge でダウンロードできます。

最も簡単な使用例:

 $ rpl old_string new_string test.txt

文字列にスペースが含まれている場合は、引用符で囲む必要があります。デフォルトではrpl大文字を処理しますが完全な単語は処理しませんが、これらのデフォルトは次のように変更できますオプション-i(大文字と小文字を区別しない)および-w(単語全体)。 複数のファイルを指定することもできます:

 $ rpl -i -w "old string" "new string" test.txt test2.txt

または、extensions-x)を指定して検索するか、recursively-R)を検索してディレクトリ:

 $ rpl -x .html -x .txt -R old_string new_string test*

-p(プロンプト)オプションを使用して、インタラクティブモードで検索/置換することもできます。

出力には、置換されたファイル/文字列の数と検索のタイプ​​(大文字と小文字の区別あり、大文字と小文字、全体または一部の単語)が表示されますが、-qquiet mode)オプション、またはさらに詳細で、-vverbose mode)オプションを使用して各ファイルとディレクトリの一致を含む行番号をリストします。

覚えておく価値のある他のオプションは、-eを許可するregular expressions(名誉escapes)なので、タブ(\t)も検索できます、改行(\n)など-fを使用して権限を強制する(もちろん、ユーザーが書き込み権限を持っている場合のみ)と-dを使用して変更時間を保持することができます `)。

最後に、どれが正確に作成されるかわからない場合は、-ssimulate mode)を使用します。

79
Fran

複数のファイルを検索して置換する方法 の提案:

また、findとsedを使用することもできますが、このPerlの小さな行が適切に機能することがわかります。

Perl -pi -w -e 's/search/replace/g;' *.php
  • -eは、次のコード行を実行することを意味します。
  • -iはインプレース編集を意味します
  • -w警告を書き込む
  • -p入力ファイルをループし、スクリプトが適用された後に各行を出力します。

私の最良の結果は、Perlとgrepを使用して得られます(ファイルに検索式があることを確認するため)

Perl -pi -w -e 's/search/replace/g;' $( grep -rl 'search' )

私はこれを使いました:

grep -r "old_string" -l | tr '\n' ' ' | xargs sed -i 's/old_string/new_string/g'
  1. old_stringを含むすべてのファイルを一覧表示します。

  2. 結果の改行をスペースで置き換えます(ファイルのリストをsedに送ることができるようにします)。

  3. それらのファイルでsedを実行して、古い文字列を新しい文字列に置き換えます。

pdate:上記の結果は、空白を含むファイル名では失敗します。代わりに、次を使用します。

grep --null -lr "old_string" | xargs --null sed -i 's/old_string/new_string/g'

15
o_o_o--

VimはExモードで使用できます。

現在のディレクトリのすべてのファイルで文字列ALFをBRAに置き換えますか?

for CHA in *
do
  ex -sc '%s/ALF/BRA/g' -cx "$CHA"
done

サブディレクトリに対して同じことを再帰的に行いますか?

find -type f -exec ex -sc '%s/ALF/BRA/g' -cx {} ';'

ファイル名が別の文字列と一致する場合にのみ置き換えますか?

for CHA in *.txt
do
  ex -sc '%s/ALF/BRA/g' -cx "$CHA"
done

文字列が特定のコンテキストで見つかった場合にのみ置き換えますか?

ex -sc 'g/DEL/s/ALF/BRA/g' -cx file

文字列が特定の行番号にある場合は置き換えますか?

ex -sc '2s/ALF/BRA/g' -cx file

複数の文字列を同じ置換で置き換える

ex -sc '%s/\vALF|ECH/BRA/g' -cx file

複数の文字列を異なる置換で置き換える

ex -sc '%s/ALF/BRA/g|%s/FOX/GOL/g' -cx file
15
Steven Penny

ユーザーの観点から見ると、この仕事を完璧に行う、ナイスでシンプルなUnixツールは qsubst です。例えば、

% qsubst foo bar *.c *.h

すべてのCファイルでfoobarに置き換えます。素敵な機能は、qsubstquery-replaceを実行することです。つまり、fooが出現するたびにそれを表示し、それを置き換えるか、ない。 [-goオプションで無条件に(質問なしで)置き換えることができます。たとえば、fooを完全に置き換えたい場合は、-wなどの他のオプションがあります。]

入手方法:qsubstは(McGillから)der Mouseによって発明され、1987年8月にcomp.unix.sources 11(7)に投稿されました。更新されたバージョンが存在します。たとえば、NetBSDバージョンqsubst.c,v 1.8 2004/11/01は、私のMacで完全にコンパイルおよび実行されます。

7
phs

ripgrep (コマンド名rg)はgrepツールですが、検索と置換もサポートしています。

$ cat ip.txt
dark blue and light blue
light orange
blue sky
$ # by default, line number is displayed if output destination is stdout
$ # by default, only lines that matched the given pattern is displayed
$ # 'blue' is search pattern and -r 'red' is replacement string
$ rg 'blue' -r 'red' ip.txt
1:dark red and light red
3:red sky

$ # --passthru option is useful to print all lines, whether or not it matched
$ # -N will disable line number prefix
$ # this command is similar to: sed 's/blue/red/g' ip.txt
$ rg --passthru -N 'blue' -r 'red' ip.txt
dark red and light red
light orange
red sky


rgはインプレースオプションをサポートしていないため、自分で行う必要があります

$ # -N isn't needed here as output destination is a file
$ rg --passthru 'blue' -r 'red' ip.txt > tmp.txt && mv tmp.txt ip.txt
$ cat ip.txt
dark red and light red
light orange
red sky


正規表現の構文と機能については、 Rust regexのドキュメント を参照してください。 -Pスイッチは PCRE2 フレーバーを有効にします。 rgはデフォルトでUnicodeをサポートしています。

$ # non-greedy quantifier is supported
$ echo 'food land bark sand band cue combat' | rg 'foo.*?ba' -r 'X'
Xrk sand band cue combat

$ # unicode support
$ echo 'fox:αλεπού,eagle:αετός' | rg '\p{L}+' -r '($0)'
(fox):(αλεπού),(eagle):(αετός)

$ # set operator example, remove all punctuation characters except . ! and ?
$ para='"Hi", there! How *are* you? All fine here.'
$ echo "$para" | rg '[[:punct:]--[.!?]]+' -r ''
Hi there! How are you? All fine here.

$ # use -P if you need even more advanced features
$ echo 'car bat cod map' | rg -P '(bat|map)(*SKIP)(*F)|\w+' -r '[$0]'
[car] bat [cod] map


grepのように、-Fオプションを使用すると、固定文字列を照合できます。sedも実装する必要があると思う便利なオプションです。

$ printf '2.3/[4]*6\nfoo\n5.3-[4]*9\n' | rg --passthru -F '[4]*' -r '2'
2.3/26
foo
5.3-29


別の便利なオプションは-Uは複数行のマッチングを可能にします

$ # (?s) flag will allow . to match newline characters as well
$ printf '42\nHi there\nHave a Nice Day' | rg --passthru -U '(?s)the.*ice' -r ''
42
Hi  Day


rgは、DOSスタイルのファイルも処理できます

$ # same as: sed -E 's/\w+(\r?)$/123\1/'
$ printf 'hi there\r\ngood day\r\n' | rg --passthru --crlf '\w+$' -r '123'
hi 123
good 123


rgのもう1つの利点は、sedよりも高速である可能性が高いことです

$ # for small files, initial processing time of rg is a large component
$ time echo 'aba' | sed 's/a/b/g' > f1
real    0m0.002s
$ time echo 'aba' | rg --passthru 'a' -r 'b' > f2
real    0m0.007s

$ # for larger files, rg is likely to be faster
$ # 6.2M sample ASCII file
$ wget https://norvig.com/big.txt    
$ time LC_ALL=C sed 's/\bcat\b/dog/g' big.txt > f1
real    0m0.060s
$ time rg --passthru '\bcat\b' -r 'dog' big.txt > f2
real    0m0.048s
$ diff -s f1 f2
Files f1 and f2 are identical

$ time LC_ALL=C sed -E 's/\b(\w+)(\s+\1)+\b/\1/g' big.txt > f1
real    0m0.725s
$ time rg --no-pcre2-unicode --passthru -wP '(\w+)(\s+\1)+' -r '$1' big.txt > f2
real    0m0.093s
$ diff -s f1 f2
Files f1 and f2 are identical
3
Sundeep

私は予行演習オプションを提供し、グロブで再帰的に動作するものを必要としており、awkおよびsedでそれを実行しようとした後、あきらめ、代わりにpythonでそれを行いました。

script は、正規表現のglobパターン(例:--glob="*.html")に一致するすべてのファイルを再帰的に検索し、置換正規表現に置き換えます。

find_replace.py [--dir=my_folder] \
    --search-regex=<search_regex> \
    --replace-regex=<replace_regex> \
    --glob=[glob_pattern] \
    --dry-run

--search-regexなどのすべての長いオプションには、対応する短いオプションがあります。つまり、-sです。 -hで実行すると、すべてのオプションが表示されます。

たとえば、これはすべての日付を2017-12-31から31-12-2017に反転します。

python replace.py --glob=myfile.txt \
    --search-regex="(\d{4})-(\d{2})-(\d{2})" \
    --replace-regex="\3-\2-\1" \
    --dry-run --verbose
import os
import fnmatch
import sys
import shutil
import re

import argparse

def find_replace(cfg):
    search_pattern = re.compile(cfg.search_regex)

    if cfg.dry_run:
        print('THIS IS A DRY RUN -- NO FILES WILL BE CHANGED!')

    for path, dirs, files in os.walk(os.path.abspath(cfg.dir)):
        for filename in fnmatch.filter(files, cfg.glob):

            if cfg.print_parent_folder:
                pardir = os.path.normpath(os.path.join(path, '..'))
                pardir = os.path.split(pardir)[-1]
                print('[%s]' % pardir)
            filepath = os.path.join(path, filename)

            # backup original file
            if cfg.create_backup:
                backup_path = filepath + '.bak'

                while os.path.exists(backup_path):
                    backup_path += '.bak'
                print('DBG: creating backup', backup_path)
                shutil.copyfile(filepath, backup_path)

            with open(filepath) as f:
                old_text = f.read()

            all_matches = search_pattern.findall(old_text)

            if all_matches:

                print('Found {} matches in file {}'.format(len(all_matches), filename))

                new_text = search_pattern.sub(cfg.replace_regex, old_text)

                if not cfg.dry_run:
                    with open(filepath, "w") as f:
                        print('DBG: replacing in file', filepath)
                        f.write(new_text)
                else:
                    for idx, matches in enumerate(all_matches):
                        print("Match #{}: {}".format(idx, matches))

                    print("NEW TEXT:\n{}".format(new_text))

            Elif cfg.verbose:
                print('File {} does not contain search regex "{}"'.format(filename, cfg.search_regex))


if __name__ == '__main__':

    parser = argparse.ArgumentParser(description='''DESCRIPTION:
    Find and replace recursively from the given folder using regular expressions''',
                                     formatter_class=argparse.RawDescriptionHelpFormatter,
                                     epilog='''USAGE:
    {0} -d [my_folder] -s <search_regex> -r <replace_regex> -g [glob_pattern]

    '''.format(os.path.basename(sys.argv[0])))

    parser.add_argument('--dir', '-d',
                        help='folder to search in; by default current folder',
                        default='.')

    parser.add_argument('--search-regex', '-s',
                        help='search regex',
                        required=True)

    parser.add_argument('--replace-regex', '-r',
                        help='replacement regex',
                        required=True)

    parser.add_argument('--glob', '-g',
                        help='glob pattern, i.e. *.html',
                        default="*.*")

    parser.add_argument('--dry-run', '-dr',
                        action='store_true',
                        help="don't replace anything just show what is going to be done",
                        default=False)

    parser.add_argument('--create-backup', '-b',
                        action='store_true',
                        help='Create backup files',
                        default=False)

    parser.add_argument('--verbose', '-v',
                        action='store_true',
                        help="Show files which don't match the search regex",
                        default=False)

    parser.add_argument('--print-parent-folder', '-p',
                        action='store_true',
                        help="Show the parent info for debug",
                        default=False)

    config = parser.parse_args(sys.argv[1:])

    find_replace(config)

Here は、スクリプトの更新バージョンであり、検索語と置換を異なる色で強調表示します。

3
ccpizza