テキストファイル内のテキストを置換したいのですが、どうすればいいでしょうか?通常は次のようなことをします
sed -i 's/text/replacement/g' path/to/the/file
問題は、text
もreplacement
もダッシュ、スラッシュ、ブラックスラッシュ、引用符などを含む複雑な文字列であることです。text
の中の必要な文字をすべてエスケープすると、すぐに読めなくなってしまいます。一方、正規表現の力は必要ありません。文字通りにテキストを置換すればいいのです
正規表現を使わずに、何かのbashコマンドでテキスト置換を行う方法はありますか?
これを行うスクリプトを書くことはむしろ些細なことですが、すでに何かが存在するはずです
82 Andrea 2012-05-09
正規表現の力を必要としないときは、使わないようにしましょう。それはそれでいいのです。 しかし、これは本当に正規表現ではありません
sed 's|literal_pattern|replacement_string|g'
なので、/
が問題なら|
を使えば、前者をエスケープする必要はありません
PS: コメントについては、Escape a string for sed search patternのStackoverflowの回答も参照してください
更新: Perlで大丈夫な方は、このように\Q
と\E
で試してみてください
perl -pe 's|\Qliteral_pattern\E|replacement_string|g'
@RedGrittyBrickも、こちらやこちらのコメントで、より強力なPerl構文で同様のトリックを提案しています
12 nik 2012-05-09
export FIND='find this'
export REPLACE='replace with this'
ruby -p -i -e "gsub(ENV['FIND'], ENV['REPLACE'])" path/to/file
ここでは唯一100%安全な解決策だから
- これは静的な置換であり、正規表現ではないので、何もエスケープする必要がありません(したがって、
sed
を使うよりも優れています) - 文字列に
}
char が含まれていても壊れません (このため、提出された Perl のソリューションよりも優れています) $FIND
ではなくENV['FIND']
が使われているので、どんな文字でも壊れません。$FIND
やRubyコードでテキストをインライン化した場合、文字列にエスケープされていない'
が含まれていると、構文エラーが発生する可能性があります
16 Nowaker 2014-08-25
replace
コマンドがこれを行います
場所を変える
replace text replacement -- path/to/the/file
To stdout:
replace text replacement < path/to/the/file
Example:
$ replace '.*' '[^a-z ]{1,3}' <<EOF
> r1: /.*/g
> r2: /.*/gi
> EOF
r1: /[^a-z ]{1,3}/g
r2: /[^a-z ]{1,3}/gi
replace
コマンドは、MySQLまたはMariaDBに付属しています
11 Derek Veit 2017-01-22
また、perlの\Q
の仕組みを使って、”quote (disable) pattern metacharacters“をすることもできます
perl -pe 'BEGIN {$text = q{your */text/?goes"here"}} s/\Q$text\E/replacement/g'
3 glenn jackman 2012-05-09
私のPerlスクリプトをチェックしてみてください
str_replace Search Replace File # replace in File in place
STDIN | str_replace Search Replace # to STDOUT
便利でしょ?私もPerlを習わないとできなかったんですが、本当に必要だからです
3 Samer Ata 2015-07-04
他にもいくつかの答えをつなぎ合わせて、これにたどり着きました
function unregex {
# This is a function because dealing with quotes is a pain.
# http://stackoverflow.com/a/2705678/120999
sed -e 's/[]\/()$*.^|[]/\\&/g' <<< "$1"
}
function fsed {
local find=$(unregex "$1")
local replace=$(unregex "$2")
shift 2
# sed -i is only supported in GNU sed.
#sed -i "s/$find/$replace/g" "$@"
perl -p -i -e "s/$find/$replace/g" "$@"
}
2 Xiong Chiamiov 2013-05-11
自分のパターンから脱却することでできるようになります。こんな感じで
keyword_raw='1/2/3'
keyword_regexp="$(printf '%s' "$keyword_raw" | sed -e 's/[]\/$*.^|[]/\\&/g')"
# keyword_regexp is now '1\/2\/3'
replacement_raw='2/3/4'
replacement_regexp="$(printf '%s' "$replacement_raw" | sed -e 's/[\/&]/\\&/g')"
# replacement_regexp is now '2\/3\/4'
echo 'a/b/c/1/2/3/d/e/f' | sed -e "s/$keyword_regexp/$replacement_regexp/"
# the last command will print 'a/b/c/2/3/4/d/e/f'
このソリューションのためのクレジットはここに行きます。https://stackoverflow.com/questions/407523/escape-a-string-for-a-sed-replace-pattern
注意1: これは空でないキーワードに対してのみ動作します。空のキーワードは sed (sed -e 's//replacement/'
) では受け付けられません
注2: 残念ながら、regexp-sを使って問題を解決するような一般的なツールを私は知りません。このようなツールは Rust や C で書くことができますが、デフォルトでは存在しません
2 VasyaNovikov 2016-04-21
これは、Hashbrownの回答(およびwefの回答から、 very similar questionを強化したものです)
様々な特殊文字や文字列(^
, .
, [
, *
, $
, \(
, \)
, \{
, \}
, \+
, \?
, &
, \1
, …などの特殊な意味の問題と、/
の区切り文字)を取り除くことで、特殊文字を取り除くことができます。具体的には、すべての文字を16進数に変換することができます。この例はその原理を示しています
$ echo -n '3.14' | xxd
0000000: 332e 3134 3.14
$ echo -n 'pi' | xxd
0000000: 7069 pi
$ echo '3.14 is a transcendental number. 3614 is an integer.' | xxd
0000000: 332e 3134 2069 7320 6120 7472 616e 7363 3.14 is a transc
0000010: 656e 6465 6e74 616c 206e 756d 6265 722e endental number.
0000020: 2020 3336 3134 2069 7320 616e 2069 6e74 3614 is an int
0000030: 6567 6572 2e0a eger..
$ echo "3.14 is a transcendental number. 3614 is an integer." | xxd -p \
| sed 's/332e3134/7069/g' | xxd -p -r
pi is a transcendental number. 3614 is an integer.
一方、当然ながら sed 's/3.14/pi/g'
は 3614
も変更されてしまいます
上記はやや単純化しすぎで、境界を考慮していません。この(やや作為的な)例を考えてみましょう
$ echo -n 'E' | xxd
0000000: 45 E
$ echo -n 'g' | xxd
0000000: 67 g
$ echo '$Q Eak!' | xxd
0000000: 2451 2045 616b 210a $Q Eak!.
$ echo '$Q Eak!' | xxd -p | sed 's/45/67/g' | xxd -p -r
&q gak!
$
(24
) と Q
(51
) が結合して 2451
を形成するので、s/45/67/g
コマンドはそれを内側から引き裂きます。2451
を2671
に変えて、&q
(26
+ 71
)となります。これは、検索テキスト、置換テキスト、ファイルのデータのバイトをスペースで区切ることで防ぐことができます。以下に定型化された解決策を示します
encode() {
xxd -p -- "$@" | sed 's/../& /g' | tr -d '\n'
}
decode() {
xxd -p -r -- "$@"
}
left=$( printf '%s' "$search" | encode)
right=$(printf '%s' "$replacement" | encode)
encode path/to/the/file | sed "s/$left/$right/g" | decode
私がencode
関数を定義したのは、その機能を3回使ったからで、そのあとは対称性のためにdecode
関数を定義しました。decode
関数を定義したくない場合は、最後の行を
encode path/to/the/file | sed "s/$left/$right/g" | xxd -p –r
encode
関数はファイル内のデータ(テキスト)のサイズを3倍にして、それをsed
を通して一行で送ることに注意してください – 最後に改行を入れることもありません。GNU sedはこれを扱えるようですが、他のバージョンでは扱えないかもしれません。また、これは placeのファイルを変更しません。出力を一時ファイルに書き込んで、それを元のファイルにコピーする必要があります (あるいは、そのための他のトリックの一つ)
追加のボーナスとして、このソリューションは複数行の検索と置換(言い換えれば、改行を含む文字列の検索と置換)を処理します
2 G-Man Says ‘Reinstate Monica’ 2020-04-01
phpのstr_replaceを使用することができます
php -R 'echo str_replace("\|!£$%&/()=?^\"'\''","replace",$argn),PHP_EOL;'<input.txt >output.txt
注意: シングルクォート '
とダブルクォート "
はエスケープする必要があります
1 simlev 2017-07-31
これはスクリプトなしで sh で行うことができます (ただし、この「ワンライナー」をスクリプトに入れた方が良いでしょう) し、標準ではない外部プログラム (私は @Nowaker の answer がインジェクションに対する安全性のおかげでとても気に入っていましたが、私がこれを必要としていたこの古い CentOS ボックスには ruby がありませんでした!)。perl
があなたのための非標準ではない限り
文字列をエスケープしようとせずに(構文的に正しく実行したり、すべての特殊文字を知っていたり、などの問題を考慮して)、すべての文字列をブランケットエンコードすることで、何も特殊な文字がないようにすることができます
cat path/to/the/file | xxd -p | tr -d '\n' \
| perl -pe "s/$(printf '%s' 'text' | xxd -p | tr -d '\n')(?=(?:.{2})*\$)/$(printf '%s' 'replacement' | xxd -p | tr -d '\n')/g" \
| xxd -p -r
これは質問者の例に合わせたもので、他のユーザは明らかに変数を使用している場合は'text'
を"$text"
に、ファイルを使用していない場合はcat path/to/the/file
をprintf '%s' "$input"
に置き換えることができます
/g
を /
に置き換えて一度に置き換えることもできますし、そうでなければ $()
の外側の正規表現を編集して、マッチャーの一部のみを「エスケープ」することもできます (例えば、s/
の後に ^
を追加して、ファイルの先頭のみにマッチするようにします)。 上記の中で、行末にマッチさせるために ^/$ が必要な場合は、エンコードを解除する必要があります:
cat path/to/the/file | xxd -p | tr -d '\n' | sed 's/0a/\n/g'\
| perl -pe "s/^$(printf '%s' 'text' | xxd -p | tr -d '\n')(?=(?:.{2})*\$)/$(printf '%s' 'replacement' | xxd -p | tr -d '\n')/g" \
| sed 's/\n/0a/g' | xxd -p -r
ファイル内のすべての行を’text’で始まり、代わりに’replacement’で始まるように置き換えます
Test:
^/.[a]|$0\\{7}!!^/.[a]|$0\\{7}!!^/.[a]|$0\\{7}
の中で、リテラル^/.[a]|$0\\{7}
をリテラル$0\\
に置き換える
printf '%s' '^/.[a]|$0\\{7}!!^/.[a]|$0\\{7}!!^/.[a]|$0\\{7}' \
| xxd -p | tr -d '\n' \
| perl -pe "s/$(printf '%s' '^/.[a]|$0\\{7}' | xxd -p | tr -d '\n')(?=(?:.{2})*\$)/$(printf '%s' '$0\\' | xxd -p | tr -d '\n')/g" \
| xxd -p -r
Output:
$0\\!!$0\\!!$0\\
1 Hashbrown 2020-01-23
Node.JSの@Nowakerと同等のもの
export FNAME='moo.txt'
export FIND='search'
export REPLACE='rpl'
node -e 'fs=require("fs");fs.readFile(process.env.FNAME,"utf8",(err,data)=>{if(err!=null)throw err;fs.writeFile(process.env.FNAME,data.replace(process.env.FIND,process.env.REPLACE),"utf8",e=>{if(e!=null)throw e;});});'
0 A T 2018-07-09
もう一つの「ほぼ」作業方法をご紹介します
viまたはvimを使用します
置換したテキストファイルを作成します
:%sno/my search string \\"-:#2;g('.j');\\">/my replacestring=\\"bac)(o:#46;\\">/ :x
で、コマンドラインから vi や vim を実行します
vi -S commandfile.txt path/to/the/file
:%sno は、魔法を使わずに検索と置換を行う vi コマンドです
/ は私が選んだ区切り文字です
x は保存して終了します
バックスラッシュ’をエスケープする必要があります。フォワードラッシュ’/’は、例えば疑問符’?’や検索や置換文字列にない何かに置き換えることができます
ref: https://stackoverflow.com/questions/6254820/perform-a-non-regex-search-replace-in-vim https://vim.fandom.com/wiki/Search_without_need_to_escape_slash http://linuxcommand.org/lc3_man_pages/vim1.html
0 Samuel Åslund 2019-08-20
シンプルなPythonスクリプトを使用しています
最近ではほとんどのシステムに python が用意されています。そこで、ここに簡単なスクリプトがあります
# replace.py
# USAGE: python replace.py bad-word good-word target-file.txt
#
import sys
search_term = sys.argv[1]
replace_term = sys.argv[2]
target_file = sys.argv[3]
with open(target_file, 'r') as file:
content = file.read()
content = content.replace(sys.argv[1], sys.argv[2])
with open(target_file, 'w') as file:
file.write(content)
一つ注意点があります: これは、良い言葉や悪い言葉がすでにシステムや環境の変数に入っている場合に有効です。ただ、スクリプトに渡す際には、変数をダブルクォートで囲むようにしてください
For example:
python replace.py "$BAD_WORD" "$GOOD_WORD" target-file.txt
しかし、これらはうまくいきません
# This breaks on $ or " characters
BAD_WORD="your-artibrary-string"
# This breaks on ' characters
BAD_WORD='your-artibrary-string'
# This breaks on spaces plus a variety of characters
BAD_WORD=your-artibrary-string
任意のリテラル文字の取り扱い
1.文字をディスクに書き込む
スクリプトに任意のリテラル値を指定する必要がある場合(エスケープを省略して)、一般的にはこの方法でディスクに書き込みます
head -c -1 << 'CRAZY_LONG_EOF_MARKER' | tee /path/to/file > /dev/null
arbitrary-one-line-string
CRAZY_LONG_EOF_MARKER
… where:
- リテラルテキストを書くために、Here Documentという仕組みを採用しています
- Here Docsが作成する末尾の改行を削除するために、
head
とtee
を使用しています - EOLマーカー文字列を引用することで、Here Doc内の変数のevalutionを防止しています
トリッキーなキャラを使った簡単なデモを紹介します
head -c -1 << 'CRAZY_LONG_EOF_MARKER' | tee /path/to/file > /dev/null
1"2<3>4&5'6$7 # 8
CRAZY_LONG_EOF_MARKER
2.修正したPythonスクリプトを使用します
ワードファイルから読み取るスクリプトを更新しました
# replace.py
# USAGE: python replace.py bad-word.txt good-word.txt target-file.txt
#
import sys
search_term_file = sys.argv[1]
replace_term_file = sys.argv[2]
target_file = sys.argv[3]
print [search_term_file, replace_term_file, target_file]
with open(search_term_file, 'r') as file:
search_term = file.read()
with open(replace_term_file, 'r') as file:
replace_term = file.read()
with open(target_file, 'r') as file:
content = file.read()
print [search_term, replace_term]
content = content.replace(search_term, replace_term)
with open(target_file, 'w') as file:
file.write(content)
0 Ryan 2020-01-02
アルパインのdockerコンテナで作業をしているとき、私はpython / pearl / ruby / pythonをインストールして、検索と置換という非常に単純な操作をすることにはあまり興味がありませんでした。これらのソリューションはどれも恐ろしく複雑です!
これには2つの実行可能な解決策があります
- 他の場所からの別の検索+置換を使用してください (例: python/pearl/etc)
- すべての正規表現のメタキャラクタをエスケープします。この目的のために sed を使うことができます
私の場合はミニマムなdockerコンテナで作業しているので、1つ目はできません。 この解決策は2つ目に使えます
私の場合、ファイルには既知の文字列がありました。{{replace_me}}
とユーザ入力がありました。これを $replace_text
と呼ぶことにしましょう
sed -i "s/{{replace_me}}/$(sed 's/[&/\]/\\&/g' <<<"$replace_text")/g" path/to/file
どうやって使うの?
インプレース変換にはsed -i
を使用しています。ここでは、私は\
をデリミタとして使用していますが、これは私の置換行でエスケープしているからです。これは、ユーザがmy\string
を置くのを防ぐためです
$(sed 's/[&/\]/\\&/g' <<<"$replace_text")
ビットは、こちらこの解が派生している素晴らしい答えの中で詳しく説明されています。この場合、私はそれをワンライナーとして使用しています
OPの最初の質問の答えとして、ここでは、トリックを行う必要がありますセッドワンライナーです
sed -i "s/$(sed 's/[^^]/[&]/g; s/\^/\\^/g' <<<"$search_text")/$(sed 's/[&/\]/\\&/g' <<<"$replace_text")/g" path/to/file
でも、もう7年も経っているので、もう気にしていないのかもしれませんね
0 ThatGuyCalledRob 2020-03-26