「sed」置換に挿入された文字列がすべてのメタ文字をエスケープするかどうかを確認する方法

「sed」置換に挿入された文字列がすべてのメタ文字をエスケープするかどうかを確認する方法

テキストストリームを読み込み、後で使用できるようにsedコマンドファイルを生成するスクリプトがありますsed -f。生成されたsedコマンドは次のとおりです。

s/cid:image002\.gif@01CC3D46\.926E77E0/https:\/\/mysite.com\/files\/1922/g
s/cid:image003\.gif@01CC3D46\.926E77E0/https:\/\/mysite.com\/files\/1923/g
s/cid:image004\.jpg@01CC3D46\.926E77E0/https:\/\/mysite.com\/files\/1924/g

コマンドを生成するスクリプトが次のとおりですsed

while read cid fileid
do
    cidpat="$(echo $cid | sed -e s/\\./\\\\./g)"
    echo 's/'"$cidpat"'/https:\/\/mysite.com\/files\/'"$fileid"'/g' >> sedscr
done

cid文字列内のすべての正規表現メタ文字が正しくエスケープされ補間されるようにスクリプトを改善するにはどうすればよいですか?

ベストアンサー1

使用するエスケープ変数そしてsコマンドに対してsed(ここ$lhs$rhsそれぞれ)次の操作を行います。

escaped_lhs=$(printf '%s\n' "$lhs" | sed 's:[][\\/.^$*]:\\&:g')
escaped_rhs=$(printf '%s\n' "$rhs" | sed 's:[\\/&]:\\&:g; $!s/$/\\/')

sed "s/$escaped_lhs/$escaped_rhs/"

改行は$lhs含めることはできません。

つまり、LHSでは、すべての正規表現演算子(][.^$*)、エスケープ文字自体()、\および区切り文字(/)がエスケープされます。

&RHSでは、エスケープ、区切り文字、バックスラッシュ、および改行($!s/$/\\/最後の行()を除くすべての行の末尾にバックスラッシュを挿入することによって達成されます)のみが必要です。

注:文字の前にバックスラッシュを追加したくありません。いいえ特別な意味があります。だからこうすれば結局与える特別な意味があります。たとえば、、、およびBREでは特別な意味はありませんが、、および(およびRHSを含むfor)は<いくつか+の実装では特別な意味を持ちます。t\<\+\tsed\t

/コマンドで区切り文字として使用しsed sてアクティブにしないとします。拡張 RE-r(GNU sed// ssed/ ast)または(BSD、最も近いGNU、最も近いビジボックス)busybox sedまたは-EastPCRE-RssedまたはRE強化-A/ -X()のようにast追加のRE演算子があります。

ERE(これらの拡張の中で最も広くサポートされている拡張)の場合、その機能は次のとおりです。

escaped_lhs=$(printf '%s\n' "$lhs" | sed 's:[][\\/.^$*+?(){}|]:\\&:g')
escaped_rhs=$(printf '%s\n' "$rhs" | sed 's:[\\/&]:\\&:g; $!s/$/\\/')

sed -E "s/$escaped_lhs/$escaped_rhs/"

任意のデータを扱う際の基本的な規則は次のとおりです。

  • 使用しないでくださいecho
  • 変数参照
  • ロケール(特に文字セット)の影響を検討してください。逃げる sedsedコマンドは、コマンドと同じロケールで実行されます。使用これ脱出するsed例:文字列(同じコマンドを使用)
  • $lhs改行文字を忘れないでください(ここで改行文字が含まれていることを確認して対処できます)。

より安全なオプションは、環境に文字列を渡すperlのではなく、/正規表現演算子を使用して文字列を文字通り取得することです。sed\Q\E perl

A="$lhs" B="$rhs" perl -pe 's/\Q$ENV{A}\E/$ENV{B}/g'

perl(デフォルトでは)はロケール文字セットの影響を受けません。上記は、文字列をバイト配列としてのみ処理し、ユーザーに表示できる文字(ある場合)については気にしないからです。を使用すると、すべてのコマンドsedのロケールをwithに変更してC同じ結果を得ることができます。ただし、これはエラーメッセージの言語にも影響します。LC_ALL=Csed

一部のシェルでは、外部ユーティリティを使用せずにエスケープすることもできます。

zsh(BREエスケープの場合はこちら):

set -o extendedglob
escaped_lhs=${lhs//(#m)[][\\.^$\/&]/\\$MATCH}
escaped_rhs=${rhs//(#m)[\\&\/$'\n']/\\$MATCH}

存在するksh93

escaped_lhs=${lhs//[][\\.^$\/&]/\\\0}
escaped_rhs=${rhs//[\\&\/$'\n']/\\\0}

3.4.0+からfish

set escaped_lhs (
  string replace -ar -- '[][\\\\/.^$*]' '\\\\$0' "$lhs" |
    string collect --allow-empty
)

set escaped_rhs (
  string replace -ar -- '[\\\\&/'\n']' '\\\\$0' "$rhs" |
    string collect --allow-empty --no-trim-newlines
)

おすすめ記事