web-dev-qa-db-ja.com

sedでの正規表現(regex)の使用

これは私が理解できない一般的な主題の具体的な例です。

何年もの間、私は正規表現とsedを使用して、次のようなものを使用して、ディレクトリ内のすべてのファイル内のすべての文字列を再帰的に検索/置換しました。

#FIND $GLOBALS['timechecks'] and REPLACE with completely_different_string
shopt -s globstar dotglob;
for file in /var/www/**/*; do
  if [[ -f $file ]] && [[ -w $file ]]; then
    sed -i -- 's/\$GLOBALS\['\''timechecks'\''\]/completely_different_string/g' "$file"
  fi
done

問題は、私が知らずに逃げたbashでRegexを使用することについて基本的なことです。その結果、特定の例に対する解決策を理解できません。

私が詰まっている場所のターゲット文字列

$GLOBALS['timechecks']=addTimeCheck_sparky($GLOBALS['timechecks'], number_format(microtime(true),6,'.',''), __LINE__, basename(__FILE__));

REGEXが機能しなくなった

これは、私が思いついた検索正規表現を含むスクリプトからのsed行であり、役に立ちません。

\$GLOBALS\['\''timechecks'\''\]=addTimeCheck_sparky[(]$GLOBALS\['\''timechecks'\''\][,][ ]number_format[(]microtime[(]true[)][,]6[,]'\''\.'\''[,]'\'''\''[)][,][ ]__LINE__[],[ ]basename[(]__FILE__[)][)][;]

正規表現デバッガ

この例では、regexデバッガーを使用しました。これは、regexがターゲット文字列を見つけることを示していますが、私にとっては機能しません。デバッガーは このリンク にあります。これは、ターゲット文字列を見つけることを示す正規表現です。

\$GLOBALS\['timechecks\'\]=addTimeCheck_sparky\(\$GLOBALS\[\'timechecks\'\], number_format\(microtime\(true\),6,\'\.\',''\), __LINE__, basename\(__FILE__\)\)

正規表現デバッガの出力に関する問題:

まず、deで自分の正規表現を試しました

  1. デバッガーの正規表現をそこで実行するとなぜ動作するのかわかりませんが、bashスクリプトではそうではありません。
  2. Sedを使用したbashで正規表現に使用する方法を学んだことと比較すると、正規表現は「間違っている」ように見えます
  3. このタスクを実行するために使用するスクリプトに接続すると、デバッガーからの正規表現が機能しません。
  4. わからないので直せない

私はデバッガからの有効な正規表現をbash/sedで動作するように変換することについて、私が無知である基本的な問題だと思います。

「sashでregedをbashで使用する方法」を検索しましたが、これが潜在的な問題であるという説明は見つかりませんでした。

関連質問:ターゲット文字列を入力として受け入れ、それを見つける正規表現を提供するジェネレータがないのはなぜですか?

2
DanAllen

自動化されたソリューションが必要ですが、引用して追跡するのに多すぎるものです。

2ステップのソリューション(100%完全ではない(病理学的なコーナーケースが存在する可能性があります))は次のとおりです。

  1. 変数の文字列をそのまま取得します。

    • どうして? (引用符で囲まれた)変数(_"$var"_)の内容がシェルによって(再度)変更されることはないためです。
    • どうやって? quotedhere-stringを使用します。

    手順は次のとおりです。

    • 書き込み:_IFS= read -r var <<\END_コマンドラインで
    • 処理するまったく同じ文字列をコピーして貼り付け、Enterキーを押します
    • ENDと入力して、もう一度Enterキーを押します。

    次に、変数varには、コマンドラインでコピーした文字列とまったく同じ文字列が含まれます。変更、引用符の削除、何もありません。文字列だけです。

    あなたが見るべきものは:

    _$ IFS= read -r var <<\END
    > $GLOBALS['timechecks']=addTimeCheck_sparky($GLOBALS['timechecks'], number_format(microtime(true),6,'.',''), __LINE__, basename(__FILE__));
    > END
    _

    完了、はい、本当に、これがすべて複雑な部分です。コピーして貼り付けます。
    文字列をエコーできます:

    _$ echo "$var"
    $GLOBALS['timechecks']=addTimeCheck_sparky($GLOBALS['timechecks'], number_format(microtime(true),6,'.',''), __LINE__, basename(__FILE__));
    _

    まあ、あなたは_printf '%s\n' "$var" to avoid issues with some values of_ var _that may start with a_- `を使うほうがいいですが、この例ではエコーはうまくいきます。

この時点から、他の入力/入力/「手動エスケープ」を行う必要はありません。
以下のコマンドをコピーして貼り付けるだけです。

  1. Var値を使用して、sedで正確に一致するように使用される正確な正規表現を生成します。 sedが受け入れる正規表現の種類は POSIXによるBRE(基本正規表現) と呼ばれます。
    BREには、いくつかの特殊文字_\_ _._ _[_ _*_ _*_ _^_ _$_があります。
    これらの文字がすべて引用されている場合、正規表現は実際にはオリジナルの逐語的な文字列です。それは簡単です(_\.*^$[_):

    _$ echo "$var" | sed 's#\([\.*^$[]\)#\\\1#g'
    $GLOBALS\['timechecks']=addTimeCheck_sparky($GLOBALS\['timechecks'], number_format(microtime(true),6,'.',''), __LINE__, basename(__FILE__));
    _

    バックスラッシュ(_\_)、開始(_[_)、ドット(_._)、アスタリスク(_*_)、サーカムフレックス(_^_) )とドル記号(_$_)が存在します。これはvarのすべての可能な正規表現構成を壊し、それらすべてを単純な文字列に変換します。 「ブラケット式」(_[_)、任意の「任意の文字」(_._)、任意の繰り返し(_*_)、任意のアンカー(_^$_)、および任意のバックスラッシュを分割します(_\_)。
    _(_、_)_、_{_または_}_はエスケープする必要がないことに注意してください。エスケープされない場合、それらは残り、したがって(特別な_\(_)とは異なります。 (_\(_)をエスケープすると、それらは_\\(_になり、特別な値も失われます。

    現時点では確認できない病理学的なコーナーケースがあるかもしれませんが、単純な変換で十分であるはずの時間の99.2%で十分です。

次に、変更された文字列をキャプチャして、sedで使用できます。

_$ reg=$(echo "$var" | sed 's#\([\.*^$[]\)#\\\1#g')

$ echo "$var" | sed 's#'"$reg"'# ===any string=== #'
 ===any string=== 
_

変換が正しかった場合、sedコマンドは最初の文字列全体をキャプチャし、右側の文字列に置き換えます。

もちろん、文字列の短い部分を一致させたい場合は、一致させたい部分から始めます。

Additional変数内に正しい文字列を取得するためにどのような文字列を記述する必要があるかを知りたい場合は(追加の引用符の層が必要です)、 (bash 4.3以降)を使用できます。

_$ myvar=$(echo "${var}" | sed 's#\([\.*^$[]\)#\\\1#g')
$ echo "${myvar@Q}"
'\$GLOBALS\['\''timechecks'\'']=addTimeCheck_sparky(\$GLOBALS\['\''timechecks'\''], number_format(microtime(true),6,'\''\.'\'','\'''\''), __LINE__, basename(__FILE__));'
_

次のようなものを書いた場合:

_$ myvar='\$GLOBALS\['\''timechecks'\'']=addTimeCheck_sparky(\$GLOBALS\['\''timechecks'\''], number_format(microtime(true),6,'\''\.'\'','\'''\''), __LINE__, basename(__FILE__));'
_

引用の1つのレベルが削除され、操作に必要な文字列myvarに入ります。

あなたはあなたの最初の試みと比較して、それがどこで間違っていたかを見ることができます:

_Bad:     \$GLOBALS\['\''timechecks'\''\]=addTimeCheck_sparky[(]$GLOBALS\['\''timechecks'\''\][,][ ]number_format[(]microtime[(]true[)][,]6[,]'\''\.'\''[,]'\'''\''[)][,][ ]__LINE__[],[ ]basename[(]__FILE__[)][)][;]
Good:   '\$GLOBALS\['\''timechecks'\'']=addTimeCheck_sparky(\$GLOBALS\['\''timechecks'\''], number_format(microtime(true),6,'\''\.'\'','\'''\''), __LINE__, basename(__FILE__));'
_

これがあなたに何かを引用するための一般的なばか証明手順を与えることを願っています。

注:私は、sedの基本的なBRE正規表現に対して上記の手順を構築しました。これらは、sedが理解するすべての正規表現です(デフォルト)。 sedが_sed -E_として呼び出された場合、拡張正規表現(ERE)が使用されます。 EREにはいくつかの変更があります。特殊文字のリストは.[\()*+?{|^$になるため、エスケープする必要があります(後方参照が許可されていないため、ここでは拡張正規表現を使用できません)。

_sed 's@\([\.()*+?{|^$[]\)@\\\1@g'
_

あなたはそれがどのように機能するかを見ることができます 私が準備したこのページ

PCRE(Perl)JavaScript、PHPまたはsedがそれらを使用できないため、他の多くの正規表現フレーバーのいずれかを使用していない、ピリオド、ダメ。

関連:

BRE-POSIX基本正規表現

3
Isaac

私のために働く:

sed -- 's/\$GLOBALS\['\''timechecks'\''\]/completely_different_string/g' <<'END'
foo
$GLOBALS['timechecks']=addTimeCheck_sparky($GLOBALS['timechecks'], number_format(microtime(true),6,'.',''), __LINE__, basename(__FILE__));
bar
END
foo
completely_different_string=addTimeCheck_sparky(completely_different_string, number_format(microtime(true),6,'.',''), __LINE__, basename(__FILE__));
bar

これは、デフォルトのBSD sedとMac上のGNU sedの両方で動作します。


用語の問題:「bash sed」はありません。 bashはインタラクティブなシェルであり、プログラミング言語でもあります。 sedは別のプログラミング言語です。 bashの観点から見ると、sedはlsまたはgrepなどの$ PATHにある別のコマンドにすぎません...

5
glenn jackman