web-dev-qa-db-ja.com

sed置換パターンの文字列をエスケープする

私のbashスクリプトには、(ユーザーから受け取った)外部文字列があります。これはsedパターンで使用します。

REPLACE="<funny characters here>"
sed "s/KEYWORD/$REPLACE/g"

sedがリテラルの置き換えとして安全に受け入れられるように、$REPLACE文字列をエスケープするにはどうすればよいですか?

注:KEYWORDは、一致などしない、ダムな部分文字列です。ユーザーからは提供されません。

281

警告:これはしません改行を考慮します。より詳細な答えについては、代わりに このSO-question を参照してください。 (ありがとう、エドモートン&ニクラスピーター)

すべてをエスケープするのは悪い考えです。 Sedは、getにエスケープするために多くの文字を必要とします。たとえば、置換文字列で数字をエスケープすると、後方参照になります。

Ben Blankが言ったように、置換文字列にエスケープする必要があるのは3文字だけです(自分自身をエスケープし、ステートメントの終わりにはスラッシュ、そしてすべて置換には&を使います)。

sed -e 's/[\/&]/\\&/g'

KEYWORDという文字列をエスケープする必要がある場合は、次のものが必要です。

sed -e 's/[]\/$*.^[]/\\&/g'

/以外の文字を区切り文字として使用する場合は、使用している文字で上記の式のスラッシュを置き換える必要があります。 PeterJCLawの説明を見てください。

編集:以前は考慮されていなかったいくつかのコーナーケースが原因で、上記のコマンドが何度か変更されました。詳細は編集履歴を確認してください。

240
Pianosaurus

Sedコマンドを使用すると、/の代わりに他の文字を区切り文字として使用できます。

sed 's#"http://www\.fubar\.com"#URL_FUBAR#g'

二重引用符は問題になりません。

78
scre_www

Replace句で特別に扱われる3つのリテラル文字は、/(句を閉じるため)、\(文字をエスケープするため、後方参照、&c。)、および&(置換に一致を含めるため)です。したがって、あなたがする必要があるのはこれらの3文字をエスケープすることだけです:

sed "s/KEYWORD/$(echo $REPLACE | sed -e 's/\\/\\\\/g; s/\//\\\//g; s/&/\\\&/g')/g"

例:

$ export REPLACE="'\"|\\/><&!"
$ echo fooKEYWORDbar | sed "s/KEYWORD/$(echo $REPLACE | sed -e 's/\\/\\\\/g; s/\//\\\//g; s/&/\\\&/g')/g"
foo'"|\/><&!bar
41
Ben Blank

ピアノサウルスの正規表現に基づいて、私はキーワードと置換の両方を回避するbash関数を作りました。

function sedeasy {
  sed -i "s/$(echo $1 | sed -e 's/\([[\/.*]\|\]\)/\\&/g')/$(echo $2 | sed -e 's/[\/&]/\\&/g')/g" $3
}

使い方は次のとおりです。

sedeasy "include /etc/nginx/conf.d/*" "include /apps/*/conf/nginx.conf" /etc/nginx/nginx.conf
31
Gurpartap Singh

返答するのは少し遅れますが…ISこれをするためのもっと簡単な方法があります。区切り文字(つまり、フィールドを区切る文字)を変更するだけです。そのため、s/foo/bar/の代わりにs|bar|fooを書きます。

そして、これを行う簡単な方法は次のとおりです。

sed 's|/\*!50017 DEFINER=`snafu`@`localhost`\*/||g'

結果の出力には、その厄介なDEFINER句はありません。

16
user2460464

それはあなたが間違った質問をしているのです。私も間違った質問をしました。それが間違っている理由は最初の文の始まりです: "私のbash script ..."。

私は同じ質問をし、同じ過ちを犯しました。 bashを使っているのであれば、文字列を置き換えるのにsedを使う必要はありません(そしてbashに組み込まれたreplace機能を使うほうがmuchよりきれいです)。

次のようなものではなく、

function escape-all-funny-characters() { UNKNOWN_CODE_THAT_ANSWERS_THE_QUESTION_YOU_ASKED; }
INPUT='some long string with KEYWORD that need replacing KEYWORD.'
A="$(escape-all-funny-characters 'KEYWORD')"
B="$(escape-all-funny-characters '<funny characters here>')"
OUTPUT="$(sed "s/$A/$B/g" <<<"$INPUT")"

あなたはもっぱらbash機能を使うことができます:

INPUT='some long string with KEYWORD that need replacing KEYWORD.'
A='KEYWORD'
B='<funny characters here>'
OUTPUT="${INPUT//"$A"/"$B"}"
11
destenson

Awkを使う - それはよりきれいです:

$ awk -v R='//addr:\\file' '{ sub("THIS", R, $0); print $0 }' <<< "http://file:\_THIS_/path/to/a/file\\is\\\a\\ nightmare"
http://file:\_//addr:\file_/path/to/a/file\\is\\\a\\ nightmare
1
greggster

これは私がしばらく前に使ったAWKの例です。新しいAWKを印刷するのはAWKです。 AWKとSEDは似ているので良いテンプレートかもしれません。

ls | awk '{ print "awk " "'"'"'"  " {print $1,$2,$3} " "'"'"'"  " " $1 ".old_ext > " $1 ".new_ext"  }' > for_the_birds

それは過剰に見えますが、どういうわけか引用符の組み合わせは 'リテラルとして印刷されたものを保つのに役立ちます。それで私が正しく覚えているならば、可変要素はちょうどこのような引用符で囲まれています: "$ 1"。試してみて、それがSEDでどのように機能するのか教えてください。

0
Alex

「と」の前後のシェルの制限によって生じるすべての喜びを忘れないでください。

そう(kshで)

Var=">New version of \"content' here <"
printf "%s" "${Var}" | sed "s/[&\/\\\\*\\"']/\\&/g' | read -r EscVar

echo "Here is your \"text\" to change" | sed "s/text/${EscVar}/g"
0
NeronLeVelu

sed replaceパターンに渡すためにランダムなパスワードを生成しているという場合は、ランダムな文字列のどの文字セットに注意するかを選択します。値をbase64としてエンコードして作成されたパスワードを選択した場合は、base64で使用可能な文字と、sed replaceパターンでの特殊文字の両方が使用可能です。その文字は "/"であり、生成しているパスワードから簡単に削除されます。

# password 32 characters log, minus any copies of the "/" character.
pass=`openssl Rand -base64 32 | sed -e 's/\///g'`;
0
Mark Stosberg

タブのような特殊文字で壊れるsedeasy機能を改良しました。

function sedeasy_improved {
    sed -i "s/$(
        echo "$1" | sed -e 's/\([[\/.*]\|\]\)/\\&/g' 
            | sed -e 's:\t:\\t:g'
    )/$(
        echo "$2" | sed -e 's/[\/&]/\\&/g' 
            | sed -e 's:\t:\\t:g'
    )/g" "$3"
}

それで、違いは何ですか? $1$2は、シェルの展開を避け、タブやダブルスペースを保存するために引用符で囲まれています。

| sed -e 's:\t:\\t:g'のタブを変換する追加の:(私はトークンとして\tが好きです)。