2つの単語のいずれかを含み、両方を含まない行をgrepするにはどうすればよいですか?


25

grep2つの単語のいずれか1つのみが行に表示されている場合は、2つの単語のいずれかを含む行のみを表示しようとしますが、同じ行にある場合は表示しません。

これまでのところ試してみました grep pattern1 | grep pattern2 | ...が、期待した結果が得られませんでした。


(1)「単語」と「パターン」について話します。どっち?「quick」、「brown」、「fox」などの通常の単語、または[a-z][a-z0-9]\(,7\}\(\.[a-z0-9]\{,3\}\)+?などの正規表現 (2)単語/パターンの1つが1行に2回以上現れる(そしてもう1つが現れない)場合はどうなりますか?これは、一度出現する単語と同等ですか、それとも複数の出現としてカウントされますか?
G-Manが「Reinstate Monica」と言う

回答:


59

以外のツールgrepは、行く方法です。

たとえば、perlを使用すると、コマンドは次のようになります。

perl -ne 'print if /pattern1/ xor /pattern2/'

perl -nestdinの各行で指定されたコマンドを実行します。この場合、行がに一致する場合/pattern1/ xor /pattern2/、または一方のパターンに一致するが他方のパターンには一致しない場合(排他的または)に出力します。

これはどちらの順序のパターンでも機能し、の複数の呼び出しよりもパフォーマンスが優れているはずでありgrep、入力も少なくなります。

または、さらに短く、awkで:

awk 'xor(/pattern1/,/pattern2/)'

またはawkのバージョンがない場合xor

awk '/pattern1/+/pattern2/==1`

4
ニース-Awk xorはGNU Awkでのみ利用可能ですか?
スチールドライバー

9
@steeldriverはい、GNUのみだと思います。または、少なくとも古いバージョンでは欠落しています。/pattern1/+/pattern2/==1ir xorが欠落している場合、それを置き換えることができます。
クリス

4
@JimL。\bパターン自体に単語境界()を入れることができ\bword\bます。
wjandrea

4
@vikingsteve特にgrepを使用する場合は、他にも多くの回答があります。しかし、仕事を終わらせたいだけの人にとっては、grepが行うすべてを実行できる他のツールがあることを知っておくと便利です。
クリス

3
@vikingsteve grepソリューションの需要はXY問題の一種であると強く思います
Hagen von Eitzen

30

GNU grepでは、両方の単語を渡して、grep両方のパターンを含む行を削除できます。

$ cat testfile.txt
abc
def
abc def
abc 123 def
1234
5678
1234 def abc
def abc

$ grep -w -e 'abc' -e 'def' testfile.txt | grep -v -e 'abc.*def' -e 'def.*abc'
abc
def

16

で試す egrep

egrep  'pattern1|pattern2' file | grep -v -e 'pattern1.*pattern2' -e 'pattern2.*pattern1'

3
次のように書くこともできますgrep -e foo -e bar | grep -v -e 'foo.*bar' -e 'bar.*foo'
グレンジャックマン

8
また、grepのmanページから注意してください。Direct invocation as either egrep or fgrep is deprecated-prefergrep -E
glenn jackman

それは私のOS @glennjackmanではありません
グランプ

1
@Grumpは本当に?それは何のOSですか?でも、POSIXは言及グレップが持つべき-f-e古いオプションもののegrepfgrepしばらくの間、引き続きサポートされます。
テルドン

1
@ terdon、POSIXはPOSIXユーティリティのパスを指定しません。ここでも、標準がgrep(その担体は-F-E-e-fPOSIXが必要とされる)です/usr/xpg4/bin。のユーティリティ/binは時代遅れのものです。
ステファンシャゼラス

12

grepperlのような正規表現(like pcregrepまたはGNUまたはast-open grep -P)をサポートする実装を使用すると、次のように1回のgrep呼び出しで実行できます。

grep -P '^(?=.*pat1)(?!.*pat2)|^(?=.*pat2)(?!.*pat1)'

それはそれと一致する行を見つけることでpat1はなくpat2、かpat2ではなくpat1

(?=...)そして(?!...)、それぞれ先読み演算子と負の先読み演算子です。技術的には、上記の例では、件名の先頭(^)が検索されますが、その後にが続き、.*pat1後に続かない.*pat2か、または同じでpat1pat2逆になります。

2回検索されるため、両方のパターンを含む行には最適ではありません。代わりに、次のようなより高度なperl演算子を使用できます。

grep -P '^(?=.*pat1|())(?(1)(?=.*pat2)|(?!.*pat2))'

(?(1)yespattern|nopattern)一致yespatternであれば1番目のキャプチャグループ(空の()上で)一致し、nopatternそうでありません。その場合は()マッチ、手段があることpat1と一致しませんでした、私たちが求めているので、pat2(正ルック先)、そして私たちは探していない pat2そうでない場合は(先にネガティブな外観)。

sed、あなたはそれを書くことができます:

sed -ne '/pat1/{/pat2/!p;d;}' -e '/pat2/p'

grep: the -P option only supports a single pattern少なくとも私がアクセスできるすべてのシステムで、最初のソリューションはで失敗します。ただし、2番目のソリューションでは+1。
クリス

1
@クリス、あなたは正しい。これはGNUに固有の制限のようgrepです。pcregrepast-open grepにはその問題はありません。倍数-eを代替RE演算子に置き換えたので、GNU grepでも動作するはずです。
ステファンシャゼル

はい、今は正常に動作します。
クリス

3

ブール用語では、次のように記述できるA xor Bを探しています。

(BではなくA)

または

(AではなくB)

一致する行が表示されている限り、出力の順序に関心があるという質問に言及していないことを考えると、A xor Bのブール展開はgrepではかなり単純です:

$ cat << EOF > foo
> a b
> a
> b
> c a
> c b
> b a
> b c
> EOF
$ grep -w 'a' foo | grep -vw 'b'; grep -w 'b' foo | grep -vw 'a';
a
c a
b
c b
b c

1
これは機能しますが、ファイルの順序が乱れます。
スパラフーク

@Sparhawk本当ですが、「スクランブル」は厳しい言葉です。;)最初にすべての 'a'マッチを順番にリストし、次にすべての 'b'マッチを順番にリストします。OPは、順序を維持することに何の関心も表明せず、単にラインを表示します。FAWK、次のステップは、可能性がありsort | uniq
ジムL.

フェアコール。私の言語が不正確だったことに同意します。元の順序が変更されることを意味するつもりでした。
スパーホーク

1
@Sparhawk ...そして、完全な開示のためにあなたの観察結果を編集しました。
ジムL.

-2

次の例:

# Patterns:
#    apple
#    pear

# Example line
line="a_apple_apple_pear_a"

これは、純粋で行うことができgrep -Euniqwc

# Grep for regex pattern, sort as unique, and count the number of lines
result=$(grep -oE 'apple|pear' <<< $line | sort -u | wc -l)

grepがPerlの正規表現でコンパイルされている場合、次の場所にパイプする必要はなく、最後に一致するものに一致させることができますuniq

# Grep for regex pattern and count the number of lines
result=$(grep -oP '(apple(?!.*apple)|pear(?!.*pear))' <<< $line | wc -l)

結果を出力します。

# Only one of the words exists if the result is < 2
((result > 0)) &&
   if (($result < 2)); then
      echo Only one word matched
   else
      echo Both words matched
   fi

ワンライナー:

(($(grep -oP '(apple(?!.*apple)|pear(?!.*pear))' <<< $line | wc -l) == 1)) && echo Only one word matched

パターンをハードコーディングしたくない場合は、要素の可変セットでパターンを組み立てることを関数で自動化できます。

これは、パイプや追加プロセスのない関数としてBashでネイティブに行うこともできますが、より複雑になり、おそらく質問の範囲外になります。


(1)誰かがPerl正規表現を使用して答えを出すのはいつかと思っていました。投稿のその部分に焦点を当て、それがどのように機能するかを説明した場合、これは良い答えになるでしょう。(2)しかし、私は残りがそれほど良くないことを恐れています。質問は、「2つの単語のいずれかを含むのみを表示する」と言います(強調を追加)。出力がlinesであると想定される場合、入力も複数行でなければならないことは理にかなっています  しかし、あなたのアプローチは一行だけを見たときにのみ機能します。…(続き)
G-Manは「Reinstate Monica」と言います

(続き)…たとえば、入力に行Big apple\npear-shaped\nが含まれる場合、出力にはこれらの行の両方が含まれます。ソリューションのカウントは2になります。長いバージョンでは「両方の単語が一致しました」(これは間違った質問に対する答えです)と報告され、短いバージョンではまったく何も言われません。(3)提案:-oここを使用すると、一致する行が非表示になり、両方の単語が同じ行に表示されるのが見えなくなるため、非常に悪い考えです。…(続き)
G-Manは「Reinstate Monica」と言います

(続き)…(4)結論:uniq/の使用sort -uと、各行の最後の出現のみに一致する派手なPerl正規表現は、この質問に対する有用な答えにはなりません。しかし、たとえ彼らがそうしたとしても、質問への回答に彼らがどのように貢献するを説明しないので、それはまだ悪い答えでしょう。(良い説明の例については、ステファン・チャゼラスの回答を参照してください。)
G-Manは、「Reinstate Monica」と

OPは、「2つの単語のいずれかを含む行のみを表示する」ことを望んでいると言います。つまり、各行を独自に評価する必要があります。なぜあなたはこれが質問に答えないと感じているのかわかりません。失敗すると思われる入力例を提供してください。
Zhro

ああ、それはあなたが意味したものですか?「入力を一度に1行ずつ読み取り、各行に対してこれらの2つまたは3つのコマンド実行します」?(1)それがあなたが意図したものであることは痛々しいほど不明確です。(2)痛みを伴う非効率です。いくつかのコマンド(1つ、2つ、または4つ)でファイル全体を処理する方法を示す前に4つの答えがあり、n行の入力に対して3× nコマンドを実行したい  ですか?それが機能する場合でも、不必要に高価な実行に対しては反対票を獲得します。(3)髪の毛を裂く危険性がありますが、適切な線を表示する仕事はまだしていません。
G-Manが「Reinstate Monica」と言う
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.