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


19

を使用sedして、文字列内の最初AB最初AC(包括的)の間にあるものをに置き換えXXXます。

以下の場合の例、私はこの文字列を持っている(この文字列はテスト用です):

ssABteAstACABnnACss

そして、次のような出力が欲しいです:ssXXXABnnACss


私はこれをしましたperl

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

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

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

2
これは意味がありません。Perlで有効なソリューションがありますが、Sedを使用したいのはなぜですか?
クサラナナンダ

回答:


13

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

明らかに私たちがやりたいことはマッチです

  1. AB
    その後に
  2. 以外の任意の量AC
    続きます
  3. AC

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

  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を設定してはいけません。)

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

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


あなたの答えを編集して、長い説明を追加しました。自由にトリミングまたはロールバックしてください。
G-Manは「Reinstate Monica」と言う

@ G-Manそれは素晴らしい説明です!とてもうまくできました。ありがとうございました。
ジョン1024

次のことができますechoまたはprintfbashでうまく`\ 000' (または入力ファイルから来ることができました)。しかし、一般的に、テキストの文字列にはもちろんNULが含まれない可能性があります。
-ilkkachu

@ilkkachuあなたはそれについて正しいです。私が書いておくべきことは、シェル変数またはパラメーターにNULを含めることはできないということです。回答が更新されました。
-John1024

再び変更ACAC@て戻った場合、これは非常に安全になりませんか?
マイケルVehrs 16

7

いくつかのsed実装はそれをサポートしています。ssedPCREモードがあります:

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環境変数など)。


6

いいえ、sed正規表現には貪欲でないマッチングはありません。

あなたは、最初の発生にすべてのテキストを一致させることができAC、「含有していない何も使用してAC続く」をACPerlのと同じ処理を行い、.*?AC。問題は、「を含まACないもの」は正規表現として簡単に表現できないことです。正規表現の否定を認識する正規表現は常に存在しますが、否定の正規表現はすぐに複雑になります。そして、ポータブルsedでは、これはまったく不可能です。否定正規表現では、拡張正規表現(awkなど)に存在するが、ポータブル基本正規表現にはない代替をグループ化する必要があるためです。GNU sedなどの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では機能しません。GNUsedは非組み込みLinuxでのsed実装です。GNU sed \nでは、代わりに使用できます:

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

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

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


@ilkkachuいいえ、そうではありません。s/\n//gすべての改行を削除します。
ジル 'SO-悪であるのをやめる'

asdf。そう、私の悪い。
-ilkkachu

3

sed-Christoph Sieghartによる貪欲でないマッチング

sedで貪欲でない一致を取得するためのトリックは、一致を終了する文字を除くすべての文字を一致させることです。簡単なことはわかっていますが、貴重な時間を無駄にしました。結局、シェルスクリプトは迅速で簡単なはずです。だから誰かがそれを必要とするかもしれない場合:

貪欲なマッチング

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

貪欲でないマッチング

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


2
「簡単な」という用語はあいまいです。この場合、あなた(またはChristoph Sieghart)がこれを熟考したかどうかは明らかではありません。特に、質問の特定の問題(式のゼロの後複数の文字が続くを解決する方法を示していれば良かったでしょう。この場合、この答えはうまく機能しないことがあります。
スコット

うさぎの穴は、一見すると思ったよりもずっと深いです。あなたは正しい、その回避策は複数文字の正規表現に対してうまく機能しません。
グレソリオ

0

あなたの場合、この方法で閉じ文字を無効にすることができます:

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

2
質問は「私が最初の間は何も置き換えたい、と言うABと、最初の発生ACとをXXX、...」となりますssABteAstACABnnACssよう例を入力。この回答はその例では有効ですが、一般的な質問には回答しません。たとえばssABteCstACABnnACss、出力も生成する必要aaXXXABnnACssがありますが、コマンドはこの行を変更せずに渡します。
G-Manが「Reinstate Monica」と言う
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.