根据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 -兼容。