【问题标题】:How can I get "grep -zoP" to display every match separately?如何让“grep -zoP”分别显示每场比赛?
【发布时间】:2021-03-06 04:15:10
【问题描述】:

我在这个表格上有一个文件:

X/this is the first match/blabla
X-this is
the second match-

and here we have some fluff.

我想提取出现在“X”之后和相同标记之间的所有内容。所以如果我有“X+match+”,我想得到“match”,因为它出现在“X”之后和标记“+”之间。

所以对于给定的示例文件,我希望得到这个输出:

this is the first match

然后

this is
the second match

我设法获得了 X 之间的所有内容,后跟一个标记:

grep -zPo '(?<=X(.))(.|\n)+(?=\1)' file

即:

  • grep -Po '(?&lt;=X(.))(.|\n)+(?=\1)' 匹配 X 后跟 (something),后者被捕获并在末尾与 (?=\1) 匹配(我的代码基于 my answer here)。
  • 请注意,我使用 (.|\n) 匹配任何内容,包括新行,并且我还在 grep 中使用 -z 匹配新行。

所以这很好用,唯一的问题来自输出的显示:

$ grep -zPo '(?<=X(.))(.|\n)+(?=\1)' file
this is the first matchthis is
the second match

如您所见,所有匹配项一起出现,“这是第一个匹配项”后面是“这是第二个匹配项”,根本没有分隔符。我知道这来自“-z”的使用,它将所有文件 视为一组行,每行都以零字节(ASCII NUL 字符)而不是换行符结束(引用“人 grep")。

那么:有没有办法分别获得所有这些结果?

我也在 GNU Awk 中尝试过:

awk 'match($0, /X(.)(\n|.*)\1/, a) {print a[1]}' file

但即使是 (\n|.*) 也不起作用。

【问题讨论】:

标签: regex awk grep text-processing


【解决方案1】:

awk 不支持正则表达式定义中的反向引用。

解决方法:

$ grep -zPo '(?s)(?<=X(.)).+(?=\1)' ip.txt | tr '\0' '\n'
this is the first match
this is
the second match

# with ripgrep, which supports multiline matching
$ rg -NoUP '(?s)(?<=X(.)).+(?=\1)' ip.txt
this is the first match
this is
the second match

也可以使用(?s)X(.)\K.+(?=\1) 代替(?s)(?&lt;=X(.)).+(?=\1)。此外,您可能希望在此处使用非贪婪量词以避免匹配 match+xyz+foobaz 输入 X+match+xyz+foobaz+


perl

$ perl -0777 -nE 'say $& while(/X(.)\K.+(?=\1)/sg)' ip.txt
this is the first match
this is
the second match

【讨论】:

  • 太好了,非常感谢,关键是在找到 \0 时替换它,我没有注意到输出中提供了该字符。
【解决方案2】:

这是另一个使用 RSRT 的 gnu-awk 解决方案:

awk -v RS='X.' 'ch != "" && n=index($0, ch) {
   print substr($0, 1, n-1)
}
RT {
   ch = substr(RT, 2, 1)
}' file
this is the first match
this is
the second match

【讨论】:

  • 读了这篇文章才意识到我在假设 char 不是正则表达式 metachar - 这更健壮。
【解决方案3】:

使用 GNU awk 实现多字符 RS、RT 和 gensub(),无需将整个文件读入内存:

$ awk -v RS='X.' 'NR>1{print "<" gensub(end".*","",1) ">"} {end=substr(RT,2,1)}' file
<this is the first match>
<this is
the second match>

显然我添加了“”,这样您就可以看到每个输出记录的开始/结束位置。

以上假设X之后的字符不是非重复正则表达式元字符(例如.^[等)所以YMMV

【讨论】:

    【解决方案4】:

    用例有点问题,因为一旦打印匹配项,就会丢失有关分隔符确切位置的信息。但如果可以接受,请尝试通过管道发送到 xargs -r0

    grep -zPo '(?<=X(.))(.|\n)+(?=\1)' file | xargs -r0
    

    这些选项是 GNU 扩展,但 grep -z 和(大部分)grep -P 也是如此,所以也许这是可以接受的。

    【讨论】:

    • 完美,有时最好记住管道是关键:与其在一个巴洛克式命令上做所有事情,不如让每个部分都发挥作用。谢谢!
    【解决方案5】:

    GNU grep -z 以空字符终止输入/输出记录(与sort -z 等其他工具结合使用时很有用)。 pcregrep 不会那样做:

    pcregrep -Mo2 '(?s)X(.)(.+?)\1' file
    

    -o<em>number</em> 用于代替环视。 ? 添加了惰性量词(以防\1 稍后出现)。

    【讨论】:

      猜你喜欢
      • 2014-04-05
      • 2022-11-23
      • 2021-11-24
      • 2014-01-01
      • 2021-08-24
      • 1970-01-01
      • 2016-10-16
      • 2013-11-01
      • 1970-01-01
      相关资源
      最近更新 更多