【问题标题】:Why does this regex expression not work?为什么这个正则表达式不起作用?
【发布时间】:2013-12-05 09:22:23
【问题描述】:
$ echo '!abcae20' | grep -o -P '(?=.*\d)\w{4,}'

这将输出 nothing

但以下工作:

$ echo '!abcae20' | grep -o -P '.*?(?=.*\d)\w{4,}'

!abcae20

谁能给我一个解释?

【问题讨论】:

  • 对我来说看起来像一个错误。有趣的是,您可以使用\w*\w? 代替\w{4,},它们都会产生预期的结果。但是\w\w+ 和每个\w{k,l} 我都试过没有匹配。

标签: regex perl bash grep echo


【解决方案1】:

在您的第一个表达式中,前瞻断言将您的输入匹配为 (greedy)。

对正则表达式运行调试测试会显示以下内容。

Matching REx "(?=.*\d)\w{4,}" against "!abcae20"
0 <> <!abcae20>           |  1: IFMATCH[0](8)
0 <> <!abcae20>           |  3: STAR(5)
                                REG_ANY can match 8 times out of 2147483647...
8 <!abcae20> <>           |  5: DIGIT(6)
                                failed...
7 <!abcae2> <0>           |  5: DIGIT(6)
8 <!abcae20> <>           |  6: SUCCEED(0)
                                subpattern success...
0 <> <!abcae20>           |  8: CURLY {4,32767}(11)
                                ALNUMU can match 0 times out of 2147483647...
                                failed...
Match failed
  • 解释导致匹配失败的原因..

    1. !abcae20>

      The greedy quantifier first matches as much as possible. 
      So the .* here is matching the entire string. 
      
    2. !abcae20>

      Then tries to match any numeric character following, 
      but there are no characters left to match.
      
    3. !abcae20>

      So it backtracks making the greedy match, match one less 
      character leaving the  --> 0 <--  at the end unmatched.
      
    4. !abcae20>

      So it backtracks again matching one less leaving it unmatched.
      
    5. !abcae20>

      So it backtracks one more step matching one less again and failing your match.
      
  • 正则表达式解释:

    (?=             look ahead to see if there is:
     .*             any character except \n (0 or more times)
     \d             digits (0-9)
    )               end of look-ahead
    \w{4,}          word characters (a-z, A-Z, 0-9, _) (at least 4 times)
    

您的第二个表达式确实匹配! 与前面的非贪婪.*?,然后是匹配!abcae2 的前瞻断言,然后回溯以匹配您的单词字符和完整的字符串。

  • 正则表达式解释:

    .*?             any character except \n (0 or more times)
     (?=            look ahead to see if there is:
      .*            any character except \n (0 or more times)
      \d            digits (0-9)
     )              end of look-ahead
     \w{4,}         word characters (a-z, A-Z, 0-9, _) (at least 4 times)
    

【讨论】:

    【解决方案2】:
    $ echo '!abcae20' | grep -o -P '(?=.*\d)\w{4,}'
    

    在这个正则表达式中,前瞻(?=.*\d) 在字符串本身的开头捕获!abcae2,因此将尝试从字符串的开头匹配\w{4,}。但是由于有!\w不匹配,所以完全匹配失败

    可能遵循正则表达式会清除事情

    $ echo '!abcae20' | grep -o -P '(?=\w*\d)\w{4,}'
    abcae20
    

    这里的前瞻只捕获abcae2 并且匹配从a 开始,因此结果匹配abcae20


    $ echo '!abcae20' | grep -o -P '.*?(?=.*\d)\w{4,}'
    !abcae20
    

    在上面的正则表达式中,您允许! 首先被.*? 捕获,因此完全匹配。

    【讨论】:

      【解决方案3】:

      这行得通:

      echo '!abcae20' | grep -o -P '.*?(?=.*\d)\w{4,}
      

      因为.*? 匹配!(?=.*\d) 匹配abcae20,而\w{4,} 匹配abcae20。

      在这个:

      echo '!abcae20' | grep -o -P '(?=.*\d)\w{4,}'
      

      前瞻匹配!abcae20,是贪婪的。但是,\w{4,} 无法匹配 !,因此它失败了。

      这是失败的 perl 正则表达式调试输出:

      Matching REx "(?=.*\d)\w{4,}" against "!abcae20"
         0 <> <!abcae20>           |  1:IFMATCH[0](8)
         0 <> <!abcae20>           |  3:  STAR(5)
                                          REG_ANY can match 8 times out of 2147483647...
         8 <!abcae20> <>           |  5:    DIGIT(6)
                                            failed...
         7 <!abcae2> <0>           |  5:    DIGIT(6)
         8 <!abcae20> <>           |  6:    SUCCEED(0)
                                            subpattern success...
         0 <> <!abcae20>           |  8:CURLY {4,32767}(11)
                                        ALNUM can match 0 times out of 2147483647...
                                        failed...
      

      【讨论】:

      • 那么你如何解释(?=.*\d)\w*输出abcae20这一事实?
      • 前瞻匹配 !abcae20,\w* 不匹配
      • 为什么.*? 匹配!.* 不匹配?
      • @Kevin,前瞻中的.* 匹配所有内容,包括!。但是从!开始,\w{4,}就失败了。
      • @perreal:lookahead 应该是“零长度”(“环顾断言是零宽度模式,它匹配特定模式而不将其包含在 $& 中”)。 (并尝试\w? 案例。)
      【解决方案4】:

      原因:

      (?=.*\d)\w{4,}
      

      没有返回是因为第一部分:

      (?=.*\d)
      

      匹配整个表达式并且是积极的前瞻。正前瞻是不返回值的匹配项。如需更好的解释,请参阅perldoc perlre

      【讨论】:

        【解决方案5】:

        根据man pcrepattern

        如果一个模式以.*.{0,} 开头并且设置了PCRE_DOTALL 选项(相当于Perl 的/s),从而允许点匹配换行符,则该模式是隐式锚定的,因为后面的任何内容都将针对主题字符串中的每个字符位置进行尝试,因此在第一个之后的任何位置重试整体匹配是没有意义的。

        正如手册页继续提到的那样,如果 .* 在用作反向引用的括号组内,则无法使用该优化,因为在这种情况下,可能需要稍后重试整体匹配位置。正如 OP 中的模式所提到的那样,相同的论点意味着这种优化在零长度前瞻的情况下是不正确的。

        从联机帮助页中并不清楚前瞻中的.* 是否会导致隐式锚点,但这当然是可能的(尽管那将是一个错误,恕我直言)。无论出于何种原因,添加(?-s)(我认为它会关闭PCRE_DOTALL)并不会改变行为。但是,将.* 更改为其他内容可以。特别是,将其更改为 [^\d]* 会导致正则表达式具有预期的输出:

        $ echo '!abcae20' | grep -P -o '(?=[^\d]*\d)\w{4,}'
        abcae20
        

        至少有趣的是,在某些情况下,前瞻断言显然可以在没有创建隐式锚点的情况下工作,这可能会对上述分析产生一些疑问。但这可能只是与其他一些优化的交互。特别是,

        $ echo '!abcae20' | grep -P -o '(?=.*\d)a'
        a
        $
        

        如果模式被锚定,显然无法工作。另一方面,将a 更改为[ab],人们可能认为这不会改变匹配:

        $ echo '!abcae20' | grep -P -o '(?=.*\d)[ab]'
        $
        

        (非常感谢 @perreal 对这个问题进行了引人入胜的讨论。)

        最初让我认为这可能是一个错误的一些观察结果是:

        $ echo '!abcde20' | grep -P -o '(?=.*\d)\w*'
        abcde20
        $ echo '!abcde20' | grep -P -o '(?=.*\d)\w+'
        $ echo '!abcde20' | grep -P -o '(?=.*\d)\w'
        $ echo '!abcde20' | grep -P -o '(?=.*\d)\w?'
        a
        b
        c
        d
        e
        2
        0
        

        这一切看起来不合逻辑,但如果模式是隐式锚定的,它实际上是有道理的。在第一种和最后一种情况下(\w*\w),模式将匹配输入开头的空字符串。然后grep -o 将在下一个字符位置重试该模式,并在该位置成功。在其他两种情况下(\w+\w),锚定模式会失败,所以grep 不会重试。

        尽管如此,我坚持我的断言,即隐式锚定(如果发生这种情况)是一个错误,因为手册页很清楚它是一个 优化,并且优化不应该改变行为。 (此外,它与 (?=.*\d)a 匹配不一致。)但错误可能在文档中,因为 -- 根据@perreal -- Perl 也拒绝这些匹配,而pcre 的目标是成为 Perl -兼容。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2013-05-18
          • 2010-10-21
          相关资源
          最近更新 更多