grep只能输出匹配的指定分组吗?


289

说我有一个文件:

# file: 'test.txt'
foobar bash 1
bash
foobar happy
foobar

我只想知道“ foobar”之后出现的单词,因此可以使用此正则表达式:

"foobar \(\w\+\)"

括号表示我对foobar之后的单词有特别的兴趣。但是当我执行a时grep "foobar \(\w\+\)" test.txt,我得到的行与整个正则表达式匹配,而不仅仅是“ foobar之后的单词”:

foobar bash 1
foobar happy

我更希望该命令的输出如下所示:

bash
happy

有没有办法告诉grep仅在正则表达式中输出与分组(或特定分组)匹配的项目?


4
对于那些不需要grep的用户:perl -lne 'print $1 if /foobar (\w+)/' < test.txt
Vault

Answers:


324

GNU grep具有-Pperl样式正则表达式的-o选项,以及仅打印与模式匹配的选项的选项。可以使用环顾断言(在perlre联机帮助页中的扩展模式下进行描述)将它们组合在一起,以从确定为目的匹配的部分中删除部分grep模式-o

$ grep -oP 'foobar \K\w+' test.txt
bash
happy
$

\K是短格式(和更有效的形式)(?<=pattern),你作为一个零宽度向后看要输出的文本之前断言使用。(?=pattern)可以在您要输出的文本之后用作零宽度的超前声明。

举例来说,如果你想要的字匹配foobar,你可以使用:

$ grep -oP 'foo \K\w+(?= bar)' test.txt

或(对称)

$ grep -oP '(?<=foo )\w+(?= bar)' test.txt

3
如果您的正则表达式包含多个分组,您该怎么办?(如标题所示?)
barracel

4
@barracel:我不相信你可以。时间sed(1)
2013年

1
@camh我刚刚测试过,grep -oP 'foobar \K\w+' test.txtOP 不会输出任何内容test.txt。grep版本是2.5.1。有什么问题吗?O_O
SOUser 2014年

@XichenLi:我不能说。我刚刚建立了grep v2.5.1(它很老-从2006年开始),它对我有用。
camh 2014年

@SOUser:我也遇到过-没有输出任何文件。我提交了编辑请求,在文件名之前添加了“>”以发送输出,因为这对我来说很有效。
rjchicago

39

标准grep无法做到这一点,但是GNU grep的最新版本可以做到。您可以转向sed,awk或perl。这里有一些示例,可以根据您的示例输入进行操作。在极端情况下,它们的行为略有不同。

替换foobar word other stuffword,仅在完成替换后打印。

sed -n -e 's/^foobar \([[:alnum:]]\+\).*/\1/p'

如果第一个单词是foobar,则打印第二个单词。

awk '$1 == "foobar" {print $2}'

剥去foobar如果它是第一个字,并跳过线除外; 然后在第一个空格之后剥离所有内容并打印。

perl -lne 's/^foobar\s+// or next; s/\s.*//; print'

太棒了!我以为我可以使用sed来做到这一点,但我以前从未使用过它,希望可以使用熟悉的grep。但是,由于我熟悉vim样式的搜索和replace +正则表达式,因此这些命令的语法实际上看起来非常熟悉。万分感谢。
科里·克莱恩

1
不对,吉尔斯。请参阅我的GNU grep解决方案答案。
卡姆(Camh)2011年

1
@camh:啊,我不知道GNU grep现在有完整的PCRE支持。我已纠正我的回答,谢谢。
吉尔(Gilles)

1
由于Busybox grep不支持PCRE,因此该答案对于嵌入式Linux特别有用。
克雷格·麦昆

显然,有多种方法可以完成相同的任务,但是,如果OP要求使用grep,为什么还要回答其他问题呢?另外,您的第一段是不正确的:是的,grep可以做到。
fcm

32
    sed -n "s/^.*foobar\s*\(\S*\).*$/\1/p"

-n     suppress printing
s      substitute
^.*    anything before foobar
foobar initial search match
\s*    any white space character (space)
\(     start capture group
\S*    capture any non-white space character (word)
\)     end capture group
.*$    anything after the capture group
\1     substitute everything with the 1st capture group
p      print it

1
sed示例的+1似乎比grep更好。一个评论,^$是无关紧要的,因为这.*是一场贪婪的比赛。但是,包括它们可能有助于阐明正则表达式的意图。
托尼

18

好吧,如果您知道foobar始终是第一个单词或行,那么可以使用cut。像这样:

grep "foobar" test.file | cut -d" " -f2

-ogrep 的开关已被广泛实施(比Gnu grep扩展还广泛),因此这样做grep -o "foobar" test.file | cut -d" " -f2会提高该解决方案的有效性,该解决方案比使用后向断言更可移植。
dubiousjim 2012年

我相信,你会需要grep -o "foobar .*“或grep -o "foobar \w+"
G-人

9

如果不支持PCRE,则可以通过两次调用grep获得相同的结果。例如,要在foob​​ar之后抓取单词,请执行以下操作:

<test.txt grep -o 'foobar  *[^ ]*' | grep -o '[^ ]*$'

可以像这样在foob​​ar之后将其扩展为任意单词(带有ERE以提高可读性):

i=1
<test.txt egrep -o 'foobar +([^ ]+ +){'$i'}[^ ]+' | grep -o '[^ ]*$'

输出:

1

注意索引i是从零开始的。


6

pcregrep有一个更聪明的-o选项,可让您选择要输出的捕获组。因此,使用您的示例文件,

$ pcregrep -o1 "foobar (\w+)" test.txt
bash
happy

4

使用grep不跨平台兼容,因为-P/ --perl-regexp仅在GNUgrep而不是BSDgrep上可用。

这是使用的解决方案ripgrep

$ rg -o "foobar (\w+)" -r '$1' <test.txt
bash
happy

按照man rg

-r/ --replace REPLACEMENT_TEXT用给出的文字替换所有匹配项。

替换字符串中支持捕获组索引(例如$5)和名称(例如$foo)。

相关:GH-462


2

我发现@jgshawkey的答案非常有帮助。grep虽然不是一个很好的工具,但sed是,尽管这里有一个使用grep抓取相关行的示例。

如果您不习惯sed的正则表达式语法,则它是特殊的。

这是另一个示例:此示例解析xinput的输出以获得ID整数

⎜   ↳ SynPS/2 Synaptics TouchPad                id=19   [slave  pointer  (2)]

我想要19

export TouchPadID=$(xinput | grep 'TouchPad' | sed  -n "s/^.*id=\([[:digit:]]\+\).*$/\1/p")

注意类语法:

[[:digit:]]

并且需要逃避以下 +

我假设只有一行匹配。


这正是我试图做的。谢谢!
詹姆斯

稍微简单一点的版本,没有多余的内容grep,假设“触摸板”位于“ id”的左侧:echo "SynPS/2 Synaptics TouchPad id=19 [slave pointer (2)]" | sed -nE "s/.*TouchPad.+id=([0-9]+).*/\1/p"
阿米特·奈杜
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.