web-dev-qa-db-ja.com

正規表現を含まないテキストファイルの置換

テキストファイル内の一部のテキストを置換する必要があります。通常私は次のようなことをします

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

問題は、textreplacementの両方がダッシュ、スラッシュ、ブラックスラッシュ、引用符などを含む複雑な文字列であることです。 text内の必要なすべての文字をエスケープすると、すぐに判読できなくなります。一方、正規表現の力は必要ありません。テキストを文字どおりに置き換えるだけです。

テキスト置換を行う方法はありますかなしいくつかのbashコマンドで正規表現を使用していますか?

これを行うスクリプトを作成するのは簡単ですが、既に何かが存在しているはずです。

77
Andrea

正規表現の力が必要ない場合は、使用しないでください。それは結構です。
しかし、これは実際には 正規表現 ではありません。

sed 's|literal_pattern|replacement_string|g'

したがって、/が問題である場合は、|を使用すれば、前者をエスケープする必要はありません。

PS:コメントについては、このStackoverflowの回答 sed検索パターンの文字列をエスケープする も参照してください。


UpdatePerlを使用して問題がない場合は、\Q\Eはこのように、

 Perl -pe 's|\Qliteral_pattern\E|replacement_string|g'

@RedGrittyBrickは、コメントでより強力なPerl構文を使用して同様のトリックを提案しました here または here

8
nik
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ソリューションより優れています)
  • ENV['FIND']ではなく$FINDが使用されるため、どの文字でも壊れません。 $FINDまたはRubyコードにインライン化されたテキストを使用すると、エスケープされていない'が文字列に含まれていると、構文エラーが発生する可能性があります。
14
Nowaker

replaceコマンドはこれを行います。

https://linux.die.net/man/1/replace

場所の変更:

replace text replacement -- path/to/the/file

Stdoutへ:

replace text replacement < path/to/the/file

例:

$ 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に付属しています。

9
Derek Veit

また、Perlの\Qメカニズムを使用して、「 引用(無効にする)パターンのメタ文字

Perl -pe 'BEGIN {$text = q{your */text/?goes"here"}} s/\Q$text\E/replacement/g'
3
glenn jackman

私のPerlスクリプトをチェックしてください。正規表現を暗黙的または明示的に使用せずに、必要なことを正確に実行します。

https://github.com/Samer-Al-iraqi/Linux-str_replace

str_replace Search Replace File # replace in File in place

STDIN | str_replace Search Replace # to STDOUT

とても便利ですよね?それを行うには、Perlを学ぶ必要がありました。本当に必要だからです.

3
Samer Ata

あなたのパターンをエスケープすることでそれを行うことができます。このような:

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

私は他のいくつかの答えをつなぎ合わせてこれを思いつきました:

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" "$@"
}

これをスクリプトなしで実行できます(ただし、この「ワンライナー」をスクリプトに追加する方がよいでしょう)または非標準の外部プログラム(@Nowakerの answer に対する安全性のおかげで、インジェクションですが、私がこれを必要としていたこの古いCentOSボックスにはRubyがありませんでした!)。 Perlがあなたにとって非標準でない限り

文字列をescapeエスケープせずに(そして、すべての特殊文字を知っているなど、構文的に正しく実行することによる問題を考慮せずに)、単にブランケットすることができます。すべての文字列をエンコードして、特別である可能性がないようにします。

_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_を_/_で置き換えて1回置換するか、または$()の外側の正規表現を編集して、マッチャーの一部のみを「エスケープ」することもできます(たとえば、 _^_の後に_s/_を追加して、ファイル)の先頭のみに一致させる.
上記の場合、^/$でends-of-linesに一致させる必要がある場合は、再度エンコード解除する必要があります。

_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
_

これにより、ファイル内の「テキスト」で始まるすべての行が「置換」で始まるように置き換えられます。


テスト:

_^/.[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
_

出力:

_$0\\!!$0\\!!$0\\
_
1
Hashbrown

PHPの str_replace を使用できます。

php -R 'echo str_replace("\|!£$%&/()=?^\"'\''","replace",$argn),PHP_EOL;'<input.txt >output.txt

注:単一引用符をエスケープする必要があります'および二重引用符"、しかし。

1
simlev

簡単なPythonスクリプトを使用する

ほとんどのシステムにはpythonを使用する準備ができています。このため、yaで機能する簡単なスクリプトを次に示します。

# 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)

1つの警告:これは、良い/悪い単語がすでにシステム/環境変数にある場合にうまく機能します。スクリプトに渡すときに、変数をラップするために二重引用符を使用していることを確認してください。

例えば:

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

... どこ:

  • Here Document メカニズムを使用してリテラルテキストを書き込みます
  • ヒアドキュメントが作成する末尾の改行を削除するためにheadteeを使用しています
  • EOLマーカー文字列を引用することにより、ヒアドキュメント内の変数の評価を防止しています

ここにトリッキーな文字を使った簡単なデモがあります:

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. Modified Python Scriptを使用します

Wordファイルから読み取る更新されたスクリプトは次のとおりです。

# 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

アルパインのDockerコンテナーで作業しているので、python/pearl/Ruby/python to検索と置換の非常に単純な操作を実行しますこれらのソリューションはすべて恐ろしく複雑です!!

これには2つの実行可能な解決策があります。

  1. 別の検索を使用して、他の場所から置き換えます(例:python/pearl/etc)
  2. すべての正規表現のメタ文字をエスケープします。この目的でsedを使用できます。

最小のDockerコンテナーで作業しているため、最初のケースは実行できません。
これ ソリューションは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")ビットは詳細に説明されています here このソリューションが導き出された素晴らしい答えです。この場合、私はそれをワンライナーとして使用しています

OPの最初の質問への回答として、トリックを実行するsed one linerを次に示します。

_sed -i "s/$(sed 's/[^^]/[&]/g; s/\^/\\^/g' <<<"$search_text")/$(sed 's/[&/\]/\\&/g' <<<"$replace_text")/g" path/to/file
_

しかし、それは7年になったことを考えると、おそらくもう気にしないと思います!

0

これは ハッシュブラウンの回答 (および ウェフの回答 の非常に類似した質問への拡張)の拡張です。

さまざまな特殊文字と文字列の特別な意味の問題を削除できます(_^_、_._、_[_、_*_、_$_、_\(_、_\)_、_\{_、_\}_、_\+_、_\?_、_&_、_\1_、…、何でも、および_/_ delimiter)byremoving the special characters。具体的には、すべてを16進数に変換できます。その後、_0_-_9_およびa-fのみを処理します。この例は、原理を示しています。

_$ 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を1行として送信します。 GNU sedはこれを処理できるようです。他のバージョンでは処理できない可能性があります。また、これによってファイルがインプレースで変更されることはありません。出力を一時ファイルに書き込む必要があります元のファイルにコピーします(またはそれを行うための他のトリックの1つ)。

追加のボーナスとして、このソリューションは複数行の検索と置換(つまり、改行を含む検索文字列と置換文字列)を処理します。

もう1つの「ほぼ」機能する方法。

Viまたはvimを使用します。

置換したテキストファイルを作成します。

:%sno/my検索文字列\\ "-:#2; g( '。j'); \\">/my replacestring = \\ "bac)(o:#46; \\" >/
:x 

次に、コマンドラインからviまたはvimを実行します。

vi -S commandfile.txt path/to/the/file

:%snoは、魔法を使わずに検索と置換を行うviコマンドです。

/は、選択したセパレーターです。

:xはviを保存して終了します。

バックスラッシュ「\」をエスケープする必要があります。フォワードスラッシュ「/」は次のように置き換えることができます。疑問符「?」または検索または置換文字列にない何か他のもの、パイプ '|'私のために機能しませんでした。

ref: https://stackoverflow.com/questions/6254820/perform-a-non-regex-search-replace-in-vimhttps://vim.fandom.com/ wiki/Search_without_need_to_escape_slashhttp://linuxcommand.org/lc3_man_pages/vim1.html

0
Samuel Åslund

@Nowakerに相当するNode.JS:

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