it-swarm.cn

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

说我有一个文件:

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

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

_"foobar \(\w\+\)"
_

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

_foobar bash 1
foobar happy
_

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

_bash
happy
_

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

338
Cory Klein

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

$ 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
373
camh

标准grep不能执行此操作,但是 最新版本的GNU grep可以 。您可以使用sed,awk或Perl。以下是一些执行此操作的示例您希望在示例输入中使用它们;在极端情况下,它们的行为略有不同。

替换foobar Word other stuff by Word,仅在替换完成后打印。

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'
49
    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
46
jgshawkey

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

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

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

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

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

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

可以在foobar之后将其扩展为任意Word(使用ERE表示可读性):

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

输出:

1

请注意,索引i从零开始。

9
Thor

使用grep不跨平台兼容,因为-P/--Perl-regexp仅在 GNU grep 上可用,而不是在 BSD grep 上可用。

这是使用 ripgrep 的解决方案:

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

按照 man rg

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

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

相关: GH-462

7
kenorb

我发现@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:]]

并且需要转义以下+

我假设只有一行匹配。

2
Tim Richardson