【问题标题】:Regex lookahead for 'not followed by' in grep正则表达式前瞻 grep 中的“不跟随”
【发布时间】:2012-03-01 03:40:59
【问题描述】:

我正在尝试查找所有 Ui\. 实例,后面没有 Line,甚至只是字母 L

编写正则表达式以查找特定字符串的所有实例而不是另一个字符串的正确方法是什么?

使用前瞻

grep "Ui\.(?!L)" *
bash: !L: event not found


grep "Ui\.(?!(Line))" *
nothing

【问题讨论】:

  • 正则表达式的哪个亚种 - PCRE、ERE、BRE、grep、ed、sed、perl、python、Java、C、...?
  • 顺便说一句,“未找到事件”来自使用历史扩展。如果您从不使用历史扩展,您可能希望关闭它,并且有时希望能够在交互式命令中使用感叹号。 set +o histexpand 在 Bash 或 set +H,YMMV。
  • 我也遇到了历史扩展问题。我认为我只是通过切换到单引号来解决它,所以shell不会尝试修改参数。
  • @Coderer 也解决了我的问题。谢谢。
  • @Coderer 使用单引号非常好 - 除非您希望其他(最常见的是 $)元字符处于活动状态。 '' 保护所有 metachars 免受 shell 的影响,这只是偶尔你想要的。 Tripleee 的评论是处理这个问题的最佳方式,IMO。

标签: regex grep regex-lookarounds


【解决方案1】:

您所追求的负前瞻需要比标准grep 更强大的工具。您需要启用 PCRE 的 grep。

如果您有 GNU grep,当前版本支持选项 -P--perl-regexp,然后您可以使用所需的正则表达式。

如果您没有 GNU grep(足够新的版本),请考虑获取 ack

【讨论】:

  • 我很确定在这种情况下的问题只是在 bash 中你应该使用单引号而不是双引号,因此它不会将 ! 视为特殊字符。
  • (见下文我的回答正是如此。)
  • 已验证,正确答案应结合此答案和@NHDaly 的评论。例如,这个命令对我有用: grep -P '^.*contains((?!but_not_this).)*$' *.log.* >"D:\temp\result.out"
  • 对于那些不支持-P 的人,请再次尝试将结果通过管道传输到grep --invert-match,例如:git log --diff-filter=D --summary | grep -E 'delete.*? src' | grep -E --invert-match 'xml'。确保支持@Vinicius Ottoni 的回答。
  • @wangf 我在 Cygwin 下使用 Bash,当我更改为单引号时,我仍然收到错误“未找到事件”。
【解决方案2】:

您的部分问题的答案在这里,并且 ack 的行为方式相同: Ack & negative lookahead giving errors

您正在为 grep 使用双引号,这允许 bash “将 ! 解释为历史扩展命令。”

您需要将您的模式用单引号括起来: grep 'Ui\.(?!L)' *

但是,请参阅 @JonathanLeffler's answer 以解决标准 grep 中的负前瞻问题!

【讨论】:

  • 您将 GNU grep 的扩展功能与标准 grep 的功能混淆了,其中 grep 的标准是 POSIX。你所说的也是正确的——我在禁用 C-shell 野蛮行为的情况下运行 Bash(因为如果我想要一个 C shell,我会使用一个,但我不想要一个),所以 ! 的东西不影响我——但要获得负面的前瞻,你需要非标准的grep
  • @JonathanLeffler,感谢您的澄清;我认为你是对的,它需要我们两个答案来解决所有 OP 的症状。谢谢。
  • 通过将-E 选项与此负前瞻一起使用,它会给出grep: repetition-operator operand invalid :(
【解决方案3】:

您可能无法使用 grep 执行标准的负前瞻,但通常您应该能够使用“反向”开关“-v”获得等效的行为。使用它,您可以构建一个正则表达式来补充您想要匹配的内容,然后通过 2 个 grep 对其进行管道传输。

对于有问题的正则表达式,您可能会执行类似的操作

grep 'Ui\.' * | grep -v 'Ui\.L'

【讨论】:

  • 如果该行包含 Ui.Line 和不带 .Line 的 Ui,则会排除更多的东西,更多的实例
  • (是的,这就是我不严格制定它的原因。这只是解决了将人们引导到这个问题的大部分场景,仅此而已。)
【解决方案4】:

如果您需要使用不支持负前瞻的正则表达式实现并且您不介意匹配额外的字符*,那么您可以使用negated character classes [^L]alternation |end of string anchor $

在您的情况下,grep 'Ui\.\([^L]\|$\)' * 完成了这项工作。

  • Ui\. 匹配您感兴趣的字符串

  • \([^L]\|$\) 匹配除L 以外的任何单个字符,或者匹配行尾:[^L]$

如果您想排除多个字符,那么您只需要对其进行更多的交替和否定。查找a后面没有bc

grep 'a\(\([^b]\|$\)\|\(b\([^c]\|$\)\)\)' *

要么是 (a 后跟不是 b,要么是行尾:a 然后 [^b]$) 或 (a 后跟 b后面不是c 或后面是行尾:a 然后是b,然后是[^c]$

这种表达式会变得非常笨拙且容易出错,即使是很短的字符串。您可以编写一些东西来为您生成表达式,但使用支持负前瞻的正则表达式实现可能会更容易。

*如果您的实现支持non-capturing groups,那么您可以避免捕获额外的字符。

【讨论】:

    【解决方案5】:

    至少在“Ui”之后不想要“L”字符的情况下。你真的不需要 PCRE。

        grep -E 'Ui\.($|[^L])' *
    

    在这里,我确保匹配“Ui”的特殊情况。在行尾。

    【讨论】:

      【解决方案6】:

      如果您的 grep 不支持 -P 或 --perl-regexp,您可以安装启用 PCRE 的 grep,例如“pcregrep”,它不需要像 GNU grep 这样的命令行选项来接受 Perl 兼容的正则表达式,你只需运行

      pcregrep "Ui\.(?!Line)"
      

      您不需要像示例“Ui.(?!(Line))”中的“Line”另一个嵌套组 - 外部组就足够了,就像我在上面显示的那样。

      让我再给你一个负面断言的例子:当你有行列表,由“ipset”返回时,每行显示行中间的数据包数,你不需要零数据包的行,你只需运行:

      ipset list | pcregrep "packets(?! 0 )"
      

      如果你喜欢 perl 兼容的正则表达式并且有 perl 但没有 pcregrep 或者你的 grep 不支持 --perl-regexp,你可以使用与 grep 相同的方式工作的单行 perl 脚本:

      perl -e "while (<>) {if (/Ui\.(?!Lines)/){print;};}"
      

      Perl 像 grep 一样接受标准输入,例如

      ipset list | perl -e "while (<>) {if (/packets(?! 0 )/){print;};}"
      

      【讨论】:

        猜你喜欢
        • 2015-09-13
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-10-14
        • 2010-12-17
        • 1970-01-01
        相关资源
        最近更新 更多