bash – テキストファイル内での置換 ****正規表現なしでの置換

bash regex sed text-editing

テキストファイル内のテキストを置換したいのですが、どうすればいいでしょうか?通常は次のようなことをします

sed -i 's/text/replacement/g' path/to/the/file

問題は、textreplacementもダッシュ、スラッシュ、ブラックスラッシュ、引用符などを含む複雑な文字列であることです。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コマンドがこれを行います

Just a moment...

場所を変える

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スクリプトをチェックしてみてください

GitHub - Samer-Al-iraqi/Linux-str_replace: Non-Regex text search and replace for linux
Non-Regex text search and replace for linux. Contribute to Samer-Al-iraqi/Linux-str_replace development by creating an account on GitHub.
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 コマンドはそれを内側から引き裂きます。24512671に変えて、&q26 + 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/fileprintf '%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が作成する末尾の改行を削除するために、headteeを使用しています
  • 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つの実行可能な解決策があります

  1. 他の場所からの別の検索+置換を使用してください (例: python/pearl/etc)
  2. すべての正規表現のメタキャラクタをエスケープします。この目的のために 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


タイトルとURLをコピーしました