正規表現のgrepの「続かない」の先読み


103

私は手紙が付いてUi\.いない、Lineまたは手紙だけのすべてのインスタンスをgrepしようとしていますL

別の文字列が後に続かない特定の文字列のすべてのインスタンスを見つけるための正規表現を書く適切な方法は何ですか?

先読みの使用

grep "Ui\.(?!L)" *
bash: !L: event not found


grep "Ui\.(?!(Line))" *
nothing

5
正規表現のどの亜種-PCRE、ERE、BRE、grep、ed、sed、perl、python、Java、C、...?
ジョナサンレフラー

4
余談ですが、「イベントが見つかりません」は、履歴の拡張を使用することで発生します。履歴の展開を使用しない場合はオフにし、インタラクティブコマンドで感嘆符を使用できるようにしたい場合があります。set +o histexpandバッシュまたはset +HYMMVで。
tripleee

12
歴史拡大問題もありました。私が考えて、私は単にシェルは引数をのmungeしようとしないように、単一引用符に切り替えることによってそれを解決しました。
Coderer 2012

私の問題も解決した@Coderer。ありがとう。
NHDaly 2013年

回答:


151

否定的な先読みは、標準よりも強力なツールを必要としますgrep。PCRE対応のgrepが必要です。

GNUを使用している場合grepは、現在のバージョンでオプションがサポートされている-P--perl-regexp、必要な正規表現を使用できます。

GNUの(十分に新しいバージョンの)GNUがない場合はgrep、取得することを検討してくださいack


37
この場合の問題は、bashでは二重引用符ではなく一重引用符を使用する必要があるということだけなので!、特殊文字として扱われません。
NHDaly 2013

(それを正確に説明している私の答えについては以下を参照してください。)
NHDaly 2014年

4
検証済みの正しい答えは、この答えと@NHDalyのコメントを組み合わせたものでなければなりません。たとえば、次のコマンドは私にとって機能します: grep -P '^。* contains((?!but_not_this)。)* $' * .log。*> "D:\ temp \ result.out"
wangf

3
-Pがサポートされていない場合はgrep --invert-match、パイプの結果をにもう一度試してくださいgit log --diff-filter=D --summary | grep -E 'delete.*? src' | grep -E --invert-match 'xml'。例:@Vinicius Ottoniの回答に必ず賛成票を投じてください。
Daniel Sokolowski

@wangf CygwinでBashを使用していて、単一引用符に変更しても、「イベントが見つかりません」というエラーが引き続き表示されます。
SSilk 2017年

39

あなたの問題の一部に対する答えがここにあり、ackは同じように動作します: Ackと否定先読みでエラーが発生します

grepに二重引用符を使用しているため、bashは「!履歴拡張コマンドとして解釈」できます。

パターンをSINGLE-QUOTESでラップする必要があります。 grep 'Ui\.(?!L)' *

ただし、@ JonathanLefflerの回答を参照して、標準の先読みの問題に対処してくださいgrep


あなたはGNUの拡張機能を混乱されているgrep標準の機能にgrepするための標準は、grepPOSIXです。あなたが言うこともまた真実です-私はCシェルの蛮族を無効にしてBashを実行します(Cシェルが必要な場合は使用しますが、使用したくないので)!ものは影響を受けません—しかし、否定的な先読みを取得するには、非標準のが必要grepです。
Jonathan Leffler、2014年

1
@JonathanLeffler、説明をありがとう。OPのすべての症状に対処するには、両方の回答が必要であることは正しいと思います。ありがとう。
NHDaly 2014年

10

おそらくgrepを使用して標準の否定先読みを実行することはできませんが、通常は「逆」スイッチ「-v」を使用して同等の動作を取得できるはずです。それを使用して、一致させたいものの補集合の正規表現を作成し、それを2つのgrepsにパイプすることができます。

問題の正規表現については、あなたは次のようなことをするかもしれません

grep 'Ui\.' * | grep -v 'Ui\.L'

その行にUi.Lineと.LineのないUiが含まれている場合は、より多くのものが除外されます
nafg

1
(はい、それが私が厳密に定式化しない理由です。これは、人々をこの問題に
導く

4

否定先読みをサポートしていない正規表現の実装を使用する必要があり、余分な文字*のマッチングを気にしない場合は、否定された文字クラス[^L]代替|、および文字列最後のアンカーを使用できます$

あなたのケースでgrep 'Ui\.\([^L]\|$\)' *は仕事をしません。

  • Ui\. 興味のある文字列に一致します

  • \([^L]\|$\)Lまたは以外の任意の1文字と一致します。[^L]または、行の終わりと一致します:または$

複数の文字を除外したい場合は、その文字にさらに多くの代替と否定を投げる必要があります。次がa続かないものを見つけるにはbc

grep 'a\(\([^b]\|$\)\|\(b\([^c]\|$\)\)\)' *

どちらか(aその後にnotが続く、bまたは行末が続く:athen [^b]または$)または(がa続くbが、その後がnot cが続くか、または行末が続く:athen b、then [^c]または$です。

この種の式は、非常に扱いにくく、短い文字列でもエラーが発生しやすくなります。何かを記述して式を生成することもできますが、否定的な先読みをサポートする正規表現の実装を使用する方がおそらく簡単でしょう。

*実装が非キャプチャグループをサポートしている場合、余分な文字のキャプチャを回避できます。


1

grepが-Pまたは--perl-regexpをサポートしておらず、PCRE対応のgrepをインストールできる場合(例: "pcregrep")、Perl互換の正規を受け入れるためにGNU grepなどのコマンドラインオプションは必要ありません。式、あなたはただ走る

pcregrep "Ui\.(?!Line)"

「Ui。(?!(Line))」の例のように、「Line」に別のネストされたグループは必要ありません-上で示したように、外側のグループで十分です。

否定的なアサーションを探す別の例を挙げましょう。「ipset」によって返された行のリストがあり、各行が行の途中のパケット数を示しており、パケットがゼロの行は必要ない場合は、実行:

ipset list | pcregrep "packets(?! 0 )"

perl互換の正規表現が好きでperlはあるがpcregrepがない、またはgrepが--perl-regexpをサポートしていない場合は、grepと同じように機能するperlスクリプトを1行で実行できます。

perl -e "while (<>) {if (/Ui\.(?!Lines)/){print;};}"

Perlは、grepと同じようにstdinを受け入れます。

ipset list | perl -e "while (<>) {if (/packets(?! 0 )/){print;};}"
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.