特定の検索基準に基づいてファイル内の文字列を置き換えることは、非常に一般的なタスクです。どうやって
foo
をbar
に置き換えますか?これらは、知っているディレクトリに通常のファイルのみが含まれており、非表示ではないすべてのファイルを処理したい場合です。そうでない場合は、2の方法を使用してください。
この回答のすべてのsed
ソリューションは、GNU sed
を想定しています。FreeBSDまたはOS/Xを使用している場合は、_-i
_を_-i ''
_に置き換えます。また、sed
の任意のバージョンで_-i
_スイッチを使用すると、特定のファイルシステム セキュリティへの影響 が含まれ、配布を計画しているスクリプトでは推奨されないことに注意してください。
非再帰的、このディレクトリ内のファイルのみ:
_sed -i -- 's/foo/bar/g' *
Perl -i -pe 's/foo/bar/g' ./*
_
このサブディレクトリとすべてのサブディレクトリにある再帰的な通常のファイル(隠しファイルを含む)
_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)である場合に選択されます。
非再帰的、このディレクトリ内のファイルのみ:
_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*)
_
同じ行に後でfoo
がある場合にのみ、bar
をbaz
に置き換えます。
_sed -i 's/foo\(.*baz\)/bar\1/' file
_
sed
では、\( \)
を使用すると、括弧内にあるものがすべて保存され、_\1
_でアクセスできます。このテーマにはさまざまなバリエーションがあり、そのような正規表現の詳細については、 ここ を参照してください。
入力ファイルの3d列(フィールド)にfoo
が見つかった場合にのみ、bar
をfoo
で置き換えます(空白で区切られたフィールドを想定):
_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
_
注:awk
とPerl
の両方のソリューションは、ファイル内のスペースに影響します(先頭と末尾の空白を削除し、一致する行の空白のシーケンスを1つの空白文字に変換します)。別のフィールドの場合、_$F[N-1]
_を使用します。ここで、N
は必要なフィールド番号であり、別のフィールドセパレーターの使用(_$"=":"
_は、出力フィールドセパレーターを_:
_に設定します) :
_Perl -i -F':' -ane '$F[2]=~s/foo/baz/g; $"=":";print "@F"' foo
_
foo
をbar
に置き換えるのは、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
_
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'
_はfoo
をbaz
に置き換えます)。
または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
_
foo
、bar
またはbaz
のいずれかをfoobar
に置き換えます
_sed -Ei 's/foo|bar|baz/foobar/g' file
_
または
_Perl -i -pe 's/foo|bar|baz/foobar/g' file
_
良い 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
(プロンプト)オプションを使用して、インタラクティブモードで検索/置換することもできます。
出力には、置換されたファイル/文字列の数と検索のタイプ(大文字と小文字の区別あり、大文字と小文字、全体または一部の単語)が表示されますが、-q
(quiet mode)オプション、またはさらに詳細で、-v
(verbose mode)オプションを使用して各ファイルとディレクトリの一致を含む行番号をリストします。
覚えておく価値のある他のオプションは、-e
を許可するregular expressions
(名誉escapes)なので、タブ(\t
)も検索できます、改行(\n
)など-f
を使用して権限を強制する(もちろん、ユーザーが書き込み権限を持っている場合のみ)と-d
を使用して変更時間を保持することができます `)。
最後に、どれが正確に作成されるかわからない場合は、-s
(simulate mode)を使用します。
複数のファイルを検索して置換する方法 の提案:
また、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'
old_string
を含むすべてのファイルを一覧表示します。
結果の改行をスペースで置き換えます(ファイルのリストをsed
に送ることができるようにします)。
それらのファイルでsed
を実行して、古い文字列を新しい文字列に置き換えます。
pdate:上記の結果は、空白を含むファイル名では失敗します。代わりに、次を使用します。
grep --null -lr "old_string" | xargs --null sed -i 's/old_string/new_string/g'
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
ユーザーの観点から見ると、この仕事を完璧に行う、ナイスでシンプルなUnixツールは qsubst
です。例えば、
% qsubst foo bar *.c *.h
すべてのCファイルでfoo
をbar
に置き換えます。素敵な機能は、qsubst
がquery-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で完全にコンパイルおよび実行されます。
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
私は予行演習オプションを提供し、グロブで再帰的に動作するものを必要としており、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
は、スクリプトの更新バージョンであり、検索語と置換を異なる色で強調表示します。