为什么这个正则表达式不起作用?

时间:2013-11-21 03:35:37

标签: regex perl bash grep echo

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

这将输出 nothing

但是以下工作:

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

!abcae20

有人可以给我一个解释吗?

5 个答案:

答案 0 :(得分:2)

在您的第一个表达式中,前瞻断言将您的输入与(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. &LT;的 abcae20 &GT;!

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

      Then tries to match any numeric character following, 
      but there are no characters left to match.
      
    3. &LT; !abcae2 0 &GT;

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

      So it backtracks again matching one less leaving it unmatched.
      
    5. &LT;的 abcae20 &GT;!

      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)
    

答案 1 :(得分:2)

这有效:

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 regex调试输出:

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...

答案 2 :(得分:1)

$ 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 :(得分:1)

根据man pcrepattern

  

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

正如联机帮助页所提到的那样,如果.*位于用作反向引用的括号内组中,则无法使用该优化,因为在这种情况下,可能有一点重试整个匹配一些后来的位置。相同的论点意味着在零长度前瞻的情况下,这种优化是不正确的,正如OP中的模式所提到的那样。

从联机帮助页中不清楚前瞻中的.*是否会导致隐式锚点,但它肯定是可能的(尽管那可能是一个bug,imho)。无论出于何种原因,添加(?-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兼容。

答案 4 :(得分:0)

原因:

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

没有返回任何东西是因为第一部分:

(?=.*\d)

与整个表达相匹配,是一个积极的前瞻。正向前瞻是一种不返回其值的匹配。有关更好的说明,请参阅perldoc perlre