web-dev-qa-db-ja.com

sedが置換文字列を解釈しないようにする方法はありますか?

Sedを使用してキーワードを文字列に置き換える場合、sedは置換文字列を解釈しようとします。置換文字列に「/」文字のようなsedが特殊と見なす文字が含まれている場合、当然、置換文字列にsedに動作方法を指示する文字を含めることを意図していない限り、失敗します。

例:

VAR="hi/"

sed "s/KEYWORD/$VAR/g" somefile

特殊文字の置換文字列を解釈しないようにsedに指示する方法はありますか?内容が何であっても、ファイルのキーワードを変数の内容に置き換えることができるようにしたいだけです。

14
Tal

-p(入力のループを想定)および-e(コマンドラインでプログラムを提供)を使用してsedする代わりにPerlを使用できます。 Perlを使用すると、環境変数にアクセスできますなしシェルでこれらを補間します。変数はexportedである必要があることに注意してください:

export VAR='hi/'
Perl -p -e 's/KEYWORD/$ENV{VAR}/g' somefile

変数をどこにでもエクスポートしたくない場合は、そのプロセスにのみ提供します。

PATTERN="$VAR" Perl -p -e 's/KEYWORD/$ENV{PATTERN}/g' somefile

Perlの正規表現の構文は、デフォルトではsedの構文とわずかに異なることに注意してください。

4
Antti Haapala

交換部品に含まれる特殊文字は4つだけです。 \、 &、改行および区切り文字( ref

$ VAR='abc/def&ghi\foo
next line'

$ repl=$(sed -e 's/[&\\/]/\\&/g; s/$/\\/' -e '$s/\\$//' <<<"$VAR")

$ echo "$repl"
abc\/def\&ghi\\foo\
next line

$ echo ZYX | sed "s/Y/$repl/g"
Zabc/def&ghi\foo
next lineX
4
glenn jackman

変数値の大部分を正しく処理する非常に単純な解決策は、印刷されない文字をsedの代替コマンドの区切り文字として使用することです。

viでは、Ctrl-V(より一般的には^Vと表記)を入力することにより、任意の制御文字をエスケープできます。したがって、いくつかの制御文字を使用する場合(これらの場合、区切り文字として^Aを使用することが多い)、sedコマンドは、ドロップする変数にその非印刷文字が存在する場合にのみ機能します。

したがって、"s^V^AKEYWORD^V^A$VAR^V^Ag"と入力すると、(viで)取得される内容は次のようになります。

sed "s^AKEYWORD^A$VAR^Ag" somefile

これは、$VARに非印刷文字^Aが含まれていない限り機能します。


もちろん、user input$VARの値に渡す場合、すべてのベットがオフになり、より適切にサニタイズすることになります平均的なユーザーにとって制御文字を入力するのが難しいことに頼るのではなく、徹底的に入力してください。


ただし、実際にはデリミタ文字列以外にも注意する必要があります。たとえば、&は、置換文字列に含まれる場合、「一致したテキスト全体」を意味します。たとえば、s/stu../my&/は "stuff"を "mystuff"に、 "stung"を "mystung"に置き換えます。したがって、ドロップインする変数にany文字がある場合置換文字列として、ただし、変数のリテラル値のみを使用する場合、sedで変数を置換文字列として使用する前に、データをサニタイズする必要があります。 (ただし、データのサニタイズはsedでも実行できます。)

2
Wildcard

,または|代わりに、それをセパレーターとして扱い、技術的には何でも使用できます

マニュアルページから

\cregexpc
           Match lines matching the regular expression regexp.  The  c  may
      be any character.

ご覧のとおり、区切り文字の前の\で始めて、区切り文字として使用できます。

ドキュメントから http://www.gnu.org/software/sed/manual/sed.html#The-_0022s_0022-Command

The / characters may be uniformly replaced by any other single character 
within any given s command.

The / character (or whatever other character is used in its stead) can appear in 
the regexp or replacement only if it is preceded by a \ character.

例:

sed -e 'somevar|s|foo|bar|'
echo "Hello all" | sed "s_all_user_"
echo "Hello all" | sed "s,all,user,"

echo "Hello/ World" | sed "s,Hello/,Neo,"

1
user3566929

行ベースで1行だけを置き換える場合は、printfを使用してファイル自体に置換行を追加し、その最初の行をsedの保持領域に格納し、必要に応じてドロップすることをお勧めします。これにより、特殊文字について心配する必要がまったくなくなります。 (ここでの唯一の前提は、$VARに改行なしの1行のテキストが含まれていることです。これはすでにコメントで述べたとおりです。)VARには、改行以外にanything whatsoeverおよびこれは関係なく動作します。

VAR=whatever
{ printf '%s\n' "$VAR";cat somefile; } | sed '1{h;d;};/KEYWORD/g'

printf '%s\n'は、$VARの内容を、その内容に関係なくリテラル文字列として出力し、その後に改行が続きます。 (echoは、$VARの内容がハイフンで始まる場合など、場合によっては他のことも行います。これは、echoに渡されるオプションフラグとして解釈されます。)

中括弧は、printfに渡されるときにsomefileの内容の前にsedの出力を付加するために使用されます。ここでは、中括弧をそれ自体で区切る空白文字が重要です。中括弧の前のセミコロンも同様です。

1{h;d;}; as sedコマンドは、テキストの最初の行をsedの-​​hold spaceに格納し、次にdelete(ではなく)それを印刷する)。

/KEYWORD/は、KEYWORDを含むすべての行に次のアクションを適用します。アクションはgetで、ホールドスペースの内容を取得し、パターンスペースの代わりにドロップします。つまり、現在の行全体です。 (これは行のpartのみを置き換えるためのものではありません。)ちなみに、ホールドスペースは空になりません。つまり、copiedをパターンスペースに入れ、すべてを置き換えます。ある。

anchor正規表現を使用して、単にcontains KEYWORDである行に一致しないようにする場合は、行にKEYWORD以外の何もない行のみを追加します。行頭アンカー(^)と行末アンカー($)を正規表現に:

VAR=whatever
{ printf '%s\n' "$VAR";cat somefile; } | sed '1{h;d;};/^KEYWORD$/g'
1
Wildcard

Bashのパターン置換パラメーター拡張を使用して、置換文字列内のスラッシュをバックスラッシュでエスケープできます。 Bashではスラッシュもエスケープする必要があるため、少し面倒です。

$ var='a/b/c';var="${var//\//\\/}";echo 'this is a test' | sed "s/i/$var/g"

出力

tha/b/cs a/b/cs a test

あなたcouldはパラメータ展開を直接sedコマンドに入れます:

$ var='a/b/c';echo 'this is a test' | sed "s/i/${var//\//\\/}/g"

最初のフォームはもう少し読みやすいと思います。もちろん、複数のsedコマンドで同じ置換パターンを再利用する場合は、変換を1回だけ行うのが理にかなっています。

別のオプションは、sedを使用する代わりに、awk、PerlまたはPythonで記述されたスクリプト、またはCプログラムを使用して置換を行うことです。


Pythonの簡単な例を次に示します。これは、置換されるキーワードが入力ファイルの完全な行である場合に機能します(改行は数えません)。ご覧のとおり、これは基本的にあなたのアルゴリズムと同じですBashの例ですが、入力ファイルをより効率的に読み取ります。

import sys

#Get the keyword and replacement texts from the command line
keyword, replacement = sys.argv[1:]
for line in sys.stdin:
    #Strip any trailing whitespace
    line = line.rstrip()
    if line == keyword:
        line = replacement
    print(line)
0
PM 2Ring