web-dev-qa-db-ja.com

SED正規表現との貪欲でない一致(Perlの。*をエミュレート)

sedを使用して、ABの最初のACfirstの間にある文字列のすべてをXXXで置き換えます。

exampleの場合、次の文字列があります(この文字列はテスト専用です):

ssABteAstACABnnACss

そして、私はこれに似た出力を望みます:ssXXXABnnACss


私はPerlでこれを行いました:

$ echo 'ssABteAstACABnnACss' | Perl -pe 's/AB.*?AC/XXX/'
ssXXXABnnACss

しかし、sedで実装したいと思います。以下は(Perl互換の正規表現を使用して)機能しません。

$ echo 'ssABteAstACABnnACss' | sed -re 's/AB.*?AC/XXX/'
ssXXXss
25

Sed正規表現は最長一致に一致します。 Sedには、貪欲でないものに相当するものはありません。

明らかに私たちがしたいのは試合です

  1. AB
    に続く
  2. AC以外の任意の量
    に続く
  3. AC

残念ながら、sedは#2を実行できません—少なくとも複数文字の正規表現ではできません。もちろん、@(または[123])のような単一文字の正規表現の場合、[^@]*または[^123]*を実行できます。そして、sedの制限を回避するには、ACの出現箇所をすべて@に変更してから、

  1. AB
    に続く
  2. @以外の任意の数
    に続く
  3. @

このような:

sed 's/AC/@/g; s/AB[^@]*@/XXX/; s/@/AC/g'

最後の部分は、@の一致しないインスタンスをACに戻します。

しかし、もちろん、これは無謀なアプローチです。入力には既に@文字が含まれている可能性があるため、それらを照合することで、誤検知が発生する可能性があります。ただし、シェル変数にはNUL(\x00)文字が含まれないため、上記の回避策では@の代わりにNULを使用することをお勧めします。

$ echo 'ssABteAstACABnnACss' | sed 's/AC/\x00/g; s/AB[^\x00]*\x00/XXX/; s/\x00/AC/g'
ssXXXABnnACss

NULを使用するにはGNU sedが必要です(GNU機能が有効であることを確認するには、ユーザーがシェル変数POSIXLY_CORRECTを設定していない必要があります。)

GNUの-zフラグでsedを使用して、NULで区切られた入力(find ... -print0の出力など)を処理する場合、NULはパターンスペースにないため、NULをここでの置換に適しています。 。

NULはbash変数に含めることはできませんが、printfコマンドに含めることができます。入力文字列にNULを含むすべての文字を含めることができる場合は、 StéphaneChazelasの回答 を参照してください。これにより、巧妙なエスケープメソッドが追加されます。

17
John1024

一部のsed実装は、それをサポートしています。 ssed にはPCREモードがあります:

ssed -R 's/AB.*?AC/XXX/g'

AT&T ast sed は、拡張正規表現を使用する場合、結合と否定があります:

sed -A 's/AB(.*&(.*AC.*)!)AC/XXX/g'

移植性の高い方法で、この手法を使用できます。終了文字列(ここではAC)を、開始文字列にも終了文字列にも発生しない単一文字(ここでは:など)に置き換えます。 s/AB[^:]*://、およびその文字が入力に現れる可能性がある場合は、開始文字列と終了文字列と衝突しないエスケープメカニズムを使用します。

例:

sed 's/_/_u/g; # use _ as the escape character, escape it
     s/:/_c/g; # escape our replacement character
     s/AC/:/g; # replace the end string
     s/AB[^:]*:/XXX/g; # actual replacement
     s/:/AC/g; # restore the remaining end strings
     s/_c/:/g; # revert escaping
     s/_u/_/g'

GNU sedの場合、アプローチは、改行を置換文字として使用することです。sedは一度に1行を処理するため、パターンスペースで改行が発生することはありません、そうすることができます:

sed 's/AC/\n/g;s/AB[^\n]*\n/XXX/g;s/\n/AC/g'

他のsed実装は[^\n]をサポートしていないため、通常、これは機能しません。 GNU sedを使用して、POSIX互換性が有効になっていないことを確認する必要があります(POSIXLY_CORRECT環境変数の場合など)。

7

いいえ、sed正規表現には貪欲でない一致はありません。

最初に出現するACまでのすべてのテキストを一致させるには、「ACを含まないもの」の後にACを続けます。これは、Perlの.*?ACと同じです。 。問題は、「ACを含まないもの」は正規表現として簡単に表現できないことです。正規表現の否定を認識する正規表現は常に存在しますが、否定の正規表現は複雑になります。ポータブルsedでは、これは不可能です。否定正規表現では、拡張正規表現(たとえばawk)に存在するが、ポータブル基本正規表現には存在しない代替をグループ化する必要があるためです。 GNU sedなど)の一部のバージョンには、可能なすべての正規表現を表現できるようにするBREへの拡張機能があります。

sed 's/AB\([^A]*\|A[^C]\)*A*AC/XXX/'

正規表現を否定するのは難しいため、これは一般化されません。代わりにできることは、ラインを一時的に変形することです。一部のsed実装では、改行を入力行に表示できないため、改行をマーカーとして使用できます(複数のマーカーが必要な場合は、改行の後にさまざまな文字を続けます)。

sed -e 's/AC/\
&/g' -e 's/AB[^\
]*\nAC/XXX/' -e 's/\n//g'

ただし、一部のsedバージョンの文字セットでは、バックスラッシュと改行が機能しないことに注意してください。特に、これはGNU sed、非組み込みLinuxでのsed実装)では機能しません。GNU sedでは、\n代わりに:

sed -e 's/AC/\
&/g' -e 's/AB[^\n]*\nAC/XXX/' -e 's/\n//g'

この特定のケースでは、最初のACを改行で置き換えるだけで十分です。上記のアプローチはより一般的です。

Sedのより強力なアプローチは、ラインをホールドスペースに保存し、ラインの最初の「興味深い」部分以外をすべて削除し、ホールドスペースとパターンスペースを交換するか、パターンスペースをホールドスペースに追加して繰り返すことです。ただし、これほど複雑なことを始める場合は、awkへの切り替えを検討する必要があります。 Awkにも貪欲なマッチングはありませんが、文字列を分割して、パーツを変数に保存できます。

sed-Christoph Sieghartによる貪欲でない一致

Sedで貪欲でない一致を取得するコツは、一致を終了させる文字を除くすべての文字を一致させることです。言うまでもありませんが、貴重な時間を無駄にしましたが、結局のところ、シェルスクリプトは迅速かつ簡単なはずです。したがって、他の誰かがそれを必要とする可能性がある場合:

貪欲なマッチング

% echo "<b>foo</b>bar" | sed 's/<.*>//g'
bar

貪欲でないマッチング

% echo "<b>foo</b>bar" | sed 's/<[^>]*>//g'
foobar
5
gresolio

代替手段の1つは、文字列を変更することですwant貪欲な一致

echo "ssABtCeCAstACABnnACss" | rev | sed -E "s/(.*)CA.*BA(.*)/\1CA+-+-+-+-BA\2/" | rev

revを使用して文字列を逆にし、一致基準を逆にし、sedを通常の方法で使用してから、結果を逆にします。..

ssAB-+-+-+-+ACABnnACss
0
bu5hman

解決策は非常に簡単です。 .*は貪欲ですが、完全に貪欲というわけではありません。正規表現AB.*ACに対してssABteAstACABnnACssを照合することを検討してください。 .*に続くACは、実際に一致する必要があります。問題は、.*が貪欲であるため、後続のACが最初のものではなくlastACと一致することです。 .*は最初のACを消費しますが、正規表現のリテラルACはssABteAstACABnn [〜#〜] ac [〜#〜]の最後のリテラルと一致します= ss。これが起こらないようにするには、最初のACを何かとんでもないに置き換えるだけで、2番目のものや他のものと区別できます。

echo ssABteAstACABnnACss | sed 's/AC/-foobar-/; s/AB.*-foobar-/XXX/'
ssXXXABnnACss

貪欲な.*-foobar-ssABteAst-foobar-ABnnACssの足元で停止します。これは、この-foobar-以外に-foobar-がないため、正規表現-foobar-[〜#〜]必須[〜#〜]一致する。以前の問題は、正規表現ACに2つの一致があったことですが、.*が貪欲だったため、ACの最後の一致が選択されました。ただし、-foobar-を使用すると、1つの一致のみが可能であり、この一致は.*が完全に貪欲ではないことを証明します。 .*のバス停は、.*に続く残りの正規表現でoneの一致のみが残っている場合に発生します。

間違ったAC-foobar-に置き換えられるため、最初のABの前にACが表示されると、このソリューションは失敗することに注意してください。たとえば、最初のsed置換の後、ACssABteAstACABnnACss-foobar-ssABteAstACABnnACssになります。したがって、AB.*-foobar-に対する一致は見つかりません。ただし、シーケンスが常に... AB ... AC ... AB ... AC ...の場合、このソリューションは成功します。

0
JD Graham

あなたの場合は、次のようにして閉じる文字を無効にすることができます:

echo 'ssABteAstACABnnACss' | sed 's/AB[^C]*AC/XXX/'
0
midori