如何对包含两个单词中的一个而不包含两个单词的行进行grep?


25

我试图用来grep仅显示包含两个单词中任何一个的行,如果其中只有一个出现在行中,但是如果它们不在同一行中则不会。

到目前为止,我已经尝试过 grep pattern1 | grep pattern2 | ...但没有获得预期的结果。


(1)您谈论的是“单词”和“模式”。哪有 普通单词(例如“ quick”,“ brown”和“ fox”)或正则表达式(例如)[a-z][a-z0-9]\(,7\}\(\.[a-z0-9]\{,3\}\)+?(2)如果一个单词/模式在一行中出现多次(又不出现)怎么办?那等于一个单词出现一次,还是算作多次出现?
G-Man说“恢复莫妮卡”

Answers:


59

除行之有效的工具外,还有其他工具grep

例如,使用perl,命令将是:

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

perl -ne在stdin的每一行上运行给出的命令,在这种情况下,如果它匹配/pattern1/ xor /pattern2/,则打印该行,换句话说,它匹配一个模式,但不匹配另一个模式(排他或)。

这对于任何一种模式都适用,并且比多次调用的性能要好grep,并且键入的次数也更少。

或者,甚至更短一点,用awk:

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

或没有的awk版本xor

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

4
尼斯xor-Awk仅在GNU Awk中可用吗?
steeldriver

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'
glenn jackman

8
另外,请注意grep手册页上的内容:Direct invocation as either egrep or fgrep is deprecated-更喜欢grep -E
glenn jackman

那不在我的操作系统中@glennjackman
Grump

1
@Grump真的吗?那是什么操作系统?甚至POSIX都提到 grep应该具有-f-e选项,尽管它们较旧,egrep并且fgrep将继续受支持一段时间。
terdon

1
@ terdon,POSIX未指定POSIX实用程序的路径。再次,还有,标准grep(即支撑件-F-E-e-f如POSIX要求)是/usr/xpg4/bin。中的实用程序/bin是过时的。
斯特凡Chazelas

12

使用grep支持类似perl的正则表达式(例如pcregrep,GNU或ast-open grep -P)的实现,您可以grep通过以下方法一次调用:

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

这是找到匹配的线pat1,但没有pat2,或pat2而不是pat1

(?=...)(?!...)分别是前瞻性经营者和消极前瞻性经营者。因此,从技术上讲,上面的内容查找的是主题(^)的开头,前提是该主题后面紧跟着(.*pat1而不是紧跟着).*pat2,或者带有pat1pat2颠倒了。

对于同时包含两种模式的行而言,这不是最佳选择,因为随后将对其进行两次查找。您可以改用更高级的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,至少在我可以访问的每个系统上都没有。不过,您可以为第二个解决方案+1。
克里斯

1
@克里斯,你是对的。这似乎是GNU特有的限制greppcregrep和ast-open grep没有这个问题。我已经用-e替代RE运算符替换了倍数,因此它grep现在也应该与GNU一起使用。
斯特凡Chazelas

是的,现在工作正常。
克里斯

3

用布尔术语,您正在寻找A xor B,可以写成

(A而不是B)

要么

(B,而不是A)

只要您的问题没有提到只要显示匹配的行,您就关心输出的顺序,那么在grep中,A xor B的布尔扩展非常简单:

$ 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

@Sparhawk是的,尽管“争夺”是一个苛刻的词。;)按顺序列出所有“ a”匹配项,然后依次列出所有“ b”匹配项。OP对保持订单没有任何兴趣,只显示了行即可。一汽,下一步可能是sort | uniq
Jim L.

公平电话;我同意我的语言不正确。我的意思是暗示原始顺序将被更改。
Sparhawk

1
@Sparhawk ...我在您的观察中进行了编辑,以进行全面披露。
Jim 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)但是我担心其余的情况不太好。该问题说“仅显示包含两个单词中的任何一个的 ”(添加了强调)。如果输出应该是lines则可以认为输入也必须是多行。  但是,您的方法在仅查看一行时有效。…(续)
G-Man说

(续)…例如,如果输入包含Big apple\npear-shaped\n,则输出应包含这两行。您的解决方案将获得2分;长版本将报告“两个单词都匹配”(这是对错误问题的答案),而短版本将什么也没有说。(3)一条建议:使用-o此处是一个非常糟糕的主意,因为它隐藏了包含匹配项的行,因此您看不到两个单词何时出现在同一行。…(续)
G-Man说

(续)…(4)底线:您使用uniq/ sort -u和花哨的Perl正则表达式仅匹配每行的最后一个出现并没有真正为该问题提供有用的答案。但是,即使他们做了,这仍然是一个不好的答案,因为您没有解释他们如何对回答问题做出贡献。(请参阅StéphaneChazelas的答案以作一个很好的解释的例子。)
G-Man说

OP表示,他们希望“仅显示包含两个单词中任一单词的行”,这意味着每一行都必须自己进行评估。我不明白您为什么认为这不能解决问题。请提供您可能会失败的示例输入。
Zhro

哦,是你的意思吗?“一次读取输入一行,并对每一行执行这两个或三个命令”?(1)令人痛苦的是,这就是你的意思。(2)效率极低。您面前的四个答案显示了如何使用几个命令(一个,两个或四个)处理整个文件,并且您想对n行输入运行3×  n个命令?即使可行,它也会因不必要的昂贵执行而获得拒绝投票。(3)冒着分裂头发的危险,它仍然没有表现出适当的线条。
G-Man说“恢复莫妮卡”
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.