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

Cor*_*ein 442 grep text-processing regular-expression

说我有一个文件:

# file: 'test.txt'
foobar bash 1
bash
foobar happy
foobar
Run Code Online (Sandbox Code Playgroud)

我只想知道“foobar”后面出现什么词,所以我可以使用这个正则表达式:

"foobar \(\w\+\)"
Run Code Online (Sandbox Code Playgroud)

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

foobar bash 1
foobar happy
Run Code Online (Sandbox Code Playgroud)

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

bash
happy
Run Code Online (Sandbox Code Playgroud)

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

cam*_*amh 491

GNU grep 可以-P选择 perl 样式的正则表达式,并且-o可以选择只打印与模式匹配的内容。可以使用环视断言(在 perlre 联机帮助页中的扩展模式下描述)组合这些断言,以从确定为 的目的匹配的内容中删除部分 grep 模式-o

$ grep -oP 'foobar \K\w+' test.txt
bash
happy
$
Run Code Online (Sandbox Code Playgroud)

\K(?<=pattern)您在要输出的文本之前用作零宽度后视断言的简短形式(和更有效的形式)。(?=pattern)可以在要输出的文本之后用作零宽度前瞻断言。

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

$ grep -oP 'foo \K\w+(?= bar)' test.txt
Run Code Online (Sandbox Code Playgroud)

或(为了对称)

$ grep -oP '(?<=foo )\w+(?= bar)' test.txt
Run Code Online (Sandbox Code Playgroud)

  • 如果您的正则表达式不止一个分组,您会怎么做?(正如标题所暗示的那样?) (9认同)
  • @barracel:我不相信你可以。`sed(1)` 的时间 (8认同)
  • 提到“\K”的好答案!当我使用 `(?&lt;=)` 时,grep 抱怨我的后视长度不是固定的,但是使用 `\K` 解决了这个问题。 (4认同)
  • @camh我刚刚测试了“grep -oP 'foobar \K\w+' test.txt”与OP的“test.txt”没有输出任何内容。grep 版本是 2.5.1。可能出什么问题了?奥_奥 (3认同)
  • 似乎 -P 标志至少在 Mac El Capitan 上不起作用 (2认同)

小智 89

    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
Run Code Online (Sandbox Code Playgroud)

  • 使用 `sed -nr` 和 `( )` 而不是 `\( \)` 它对我有用(Ubuntu 20.4) (5认同)
  • 对于 sed 示例,+1 似乎是比 grep 更好的工作工具。一个评论,`^` 和 `$` 是无关的,因为 `.*` 是一个贪婪的匹配。但是,包括它们可能有助于澄清正则表达式的意图。 (4认同)
  • 我必须添加“-r”作为 sed 选项才能使其工作。 (4认同)
  • 出于某种原因,这似乎不适用于 macOS sed:`echo "foobar bash 1" | sed -n "s/^.*foobar\s*\(\S*\).*$/\1/p"` 不输出任何内容。 (3认同)

Gil*_*il' 62

标准 grep 不能这样做,但最新版本的 GNU grep 可以。您可以使用 sed、awk 或 perl。以下是一些示例,它们可以对示例输入执行您想要的操作;它们在极端情况下的行为略有不同。

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

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

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

awk '$1 == "foobar" {print $2}'
Run Code Online (Sandbox Code Playgroud)

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

perl -lne 's/^foobar\s+// or next; s/\s.*//; print'
Run Code Online (Sandbox Code Playgroud)

  • @camh:啊,我不知道 GNU grep 现在有完整的 PCRE 支持。我已经更正我的答案,谢谢。 (2认同)
  • 这个答案对嵌入式 Linux 特别有用,因为 Busybox `grep` 没有 PCRE 支持。 (2认同)

小智 26

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

grep "foobar" test.file | cut -d" " -f2
Run Code Online (Sandbox Code Playgroud)


G-M*_*ca' 23

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

$ pcregrep -o1 "foobar (\w+)" test.txt
bash
happy
Run Code Online (Sandbox Code Playgroud)

  • 哇,这对我来说太神奇了,非常感谢。我在 MacOS 上,并试图以某种方式使用匹配组。我一直在尝试`zegrep`,因为我正在grepp一个大的zip文件,但也发现pcregrep会(来自`pcregrep --help`页面):`名称以.gz结尾的文件使用zlib读取。`所以我可以直接在我的 zip 文件中使用它。再次感谢! (2认同)

ken*_*orb 15

使用grep不是跨平台兼容的,因为-P/--perl-regexp仅适用于GNUgrep,而不适用于BSDgrep

这是使用的解决方案ripgrep

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

根据man rg

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

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

相关:GH-462


Tho*_*hor 10

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

<test.txt grep -o 'foobar  *[^ ]*' | grep -o '[^ ]*$'
Run Code Online (Sandbox Code Playgroud)

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

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

输出:

1
Run Code Online (Sandbox Code Playgroud)

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