sedで正規表現を使用する

sedで正規表現を使用する

これは私が理解できない一般的なトピックの具体的な例です。

長年にわたり、私は次のように正規表現と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で正規表現を使用する際に私が知らない間に逃した基本的なものがあることです。したがって、具体的な例には解決策がありません。

目標文字列に閉じ込められました。

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

私が思いついた正規表現はうまくいきません。

これは私のスクリプトのsed行と私が思いついた検索正規表現ですが、役に立ちません。

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

正規表現デバッガ

この例では、正規表現デバッガを使用していますが、正規表現は私のターゲット文字列を見つけましたが、うまくいきませんでした。デバッガは次の場所にあります。このリンク。これは私のターゲット文字列を見つけるために表示される正規表現です。

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

正規表現デバッガ出力の問題:

まず、正規表現を試しました。

  1. そこで実行すると、デバッガの正規表現が機能する理由がわかりませんが、私のbashスクリプトでは機能しません。
  2. この正規表現は、bashとsedで学んだ正規表現と比較して「間違っている」ようです。
  3. これを行うスクリプトにデバッガの正規表現を挿入しても機能しません。
  4. 理解できないから直すことができません。

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

「bashでsedで正規表現を使用する方法」を検索しましたが、これが潜在的な問題であるという事実の説明が見つかりませんでした。

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

答え1

\$GLOBALS\['\''timechecks'\''\]=addTimeCheck_sparky[(]$GLOBALS
                                                      ^

そこにはまだ脱出していない男がいた$

\['\''timechecks'\''\][,][ ]number_format[(]microtime[(]true[)]
[,]6[,]'\''\.'\''[,]'\'''\''[)][,][ ]__LINE__[],[ ]basename[(]__FILE__[)][)][;]
                                              ^^

おそらくそうする必要があります[,]

実際にエスケープしないことは$重要ではありません(少なくともGNU sedの場合)。ただし、これは[],[ ]内部にスペースがある角括弧式です[],。しかし、これは有効な正規表現なので、望むものではないため、エラーは発生しません。

ところで、実際に引用するというのは本当に痛いことです。時にはそれを避けるのが最善です。

パターンと置換文字列、テストファイルをいくつかのファイルに入れてみましょう。

$ cat pat 
$GLOBALS['timechecks']=addTimeCheck_sparky($GLOBALS['timechecks'], number_format(microtime(true),6,'.',''), __LINE__, basename(__FILE__));
$ cat repl
hello!
$ cat test.txt
foo
$GLOBALS['timechecks']=addTimeCheck_sparky($GLOBALS['timechecks'], number_format(microtime(true),6,'.',''), __LINE__, basename(__FILE__));
bar

次に、文字列をPerlに置き換えます。

$ pat=$(< pat) repl=$(< repl) perl -i.bak -pe 's/\Q$ENV{pat}/$ENV{repl}/' test.txt
$ cat test.txt
foo
hello!
bar

ファイルから文字列を読み取るときにシェルコマンドラインから文字列を引用する必要はありません。また、パターンが変数からインポートされ\Q使用される場合、パターンの特殊文字をエスケープする必要はありません。ここでは、文字列はコマンドライン引数よりもうまく機能するため、環境を介してPerlに文字列を渡します-i。 sのように、各入力行に対して指定されたスクリプトを実行するのと同じように動作を-p作成します。perlsed-i.baksed-i

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

素晴らしい。正規表現は、複数の文字列に一致するように設計されたパターンで使用されることが多く、プログラム内でどの部分が変わるのかを知ることは困難です。常に固定文字列を探している場合は、特殊文字をエスケープする方が簡単です。しかし、実際には正規表現エンジンは最初は必要ありません。一般的なUnixツールでは非常に一般的です。

コメントで次のように言及しました。

考えてみてください。行がその文字列と一致する場合は、それを置き換えるために知っておくべきことは次のとおりです。$GLOBALS['timechecks']=addTimeCheck_sparky

それはまるで

sed -- -e 's/^.*GLOBALS..timechecks..=addTimeCheck_sparky.*$/hello/' 

これを一致させ、ライン全体を交換するために使用できます。もちろん、これは#GLOBALS_atimecheckses=addTimeCheck_sparkyトリックを書き、すべての特殊文字を.。しかし、あなたはポイントを理解しています。

また、元のファイルを最初にバックアップする場合は、いつでもバックアップコピーを作成して実行してdiff original.txt processed.txt変更を確認できます。

答え2

私のために働いた:

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

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


用語の問題:「bash sed」がありません。 bashはインタラクティブシェルでもプログラミング言語でもあります。 sedは他のプログラミング言語です。 Bashの観点から、sedは$ PATHのlsorgrepまたは...のような別のコマンドです。

答え3

自動化されたソリューションが必要です。参照して追跡する項目が多すぎます。

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

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

    • なぜ? (参照された)変数()の内容は、"$var"シェルによって(再び)変更されないためです。
    • どのように?使う先頭ここで - 文字列。

    ステップは次のとおりです。

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

    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この例ではechoはうまくいきます。

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

  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コマンドは最初の文字列全体をキャプチャして右の文字列に置き換える必要があります。

もちろん、文字列の短い部分を一致させるには、一致させたい部分から始めるだけです。

追加 変数から正しい文字列を取得するためにどのような種類の文字列を作成する必要があるかを確認するには(追加の引用階層が必要です)、次のものを使用できます(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__));'

最初のレベルの参照を削除して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拡張正規表現()が使用されます。EREEREにはいくつかの変更があります。特殊文字のリストは次のように増えます。.[\()*+?{|^$したがって、エスケープは次のようになります(いいえ、ここでは逆参照を許可しないため、拡張正規表現は使用できません)。

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

どのように動作するかを見ることができます。私が準備したページ

PCRE(Perl)JavaScript、PHP、またはその他の正規表現について話すものではありません。sedはそれらを使用できません、ピリオドは役に立たない。

関連:

BRE - POSIX 基本正規表現

関連情報