在阅读本答案之前,您应该熟悉回溯机制、原子组和贪婪量词的机理。您可以在Friedl书籍中和以下链接中找到有关这些概念和特性的信息:
www.regular-expressions.info,
www.rexegg.com
所有测试都是使用全局搜索(使用
preg_match_all()
函数)进行的。
(*FAIL)
(或缩写形式(*F)
)
baabo caaco daado
caac(*FAIL)|aa.|caaco|co
[0] => aab
[1] => caaco
[2] => aad
(*FAIL)
会导致与模式中的“坏字符”完全相同的行为。例如,如果您用“R”替换它,则会得到完全相同的结果:caacR|aa.|caaco|co
。更通用的做法是,您可以使用“总是失败的子模式”(如(?!)
,(?=a(?<!a))
等)来替换(*FAIL)
。
a(来自“baabo”的第一个)
:毫不意外,第一个结果由第二个选择项找到。(aab
)
c(第一个)
:正则表达式引擎遇到第一个“c”,尝试第一个选择项并找到:caac
,但强制使子模式失败。然后正则表达式引擎(始终从第一个“c”开始)尝试失败的第二个选择项,第三个选择项成功。(caaco
)
a(来自“daado”的第一个)
:第三个结果由第二个选择项找到。(aad
)
(*SKIP)
baabo caaco daado
caa(*SKIP)c(*FAIL)|aa.|caaco|co
[0] => aab
[1] => co
[2] => aad
这个动词定义了一个点,当子模式后面失败时,正则表达式引擎不允许回溯。因此,所有在子模式中找到的字符都会被一次性地消耗掉,并且不能用于模式(替代方案)的其他部分。
a(来自“baabo”的第一个)
:第二个替代方案找到了第一个结果。(aab
)
c(第一个)
:正则表达式引擎像第一种情况一样找到了caac
,然后失败(因为(*FAIL)
动词),回溯到第二个“c”,但不允许回溯到先前匹配的字符("caa")在(*SKIP)
动词之前。
c(第二个)
:现在,正则表达式引擎总是尝试第一个替代方案,但在这个新位置上失败了,因为后面跟着一个“o”,而不是一个“a”,然后它回溯到这个第二个“c”。请注意,在这种情况下,这些字符不像以前那样被消耗,因为子模式已经在到达(*SKIP)
动词之前失败了。
测试第二个替代方案失败了(不以“c”开头)。第三个替代方案也失败了,因为下一个字符是“o”,而不是“a”。
第四个替代方案成功并给出第二个结果。(co
)
a(来自“daado”的第一个)
:第二个替代方案找到了第三个结果。(aad
)
(*PRUNE)
baabo caaco daado
caa(*PRUNE)c(*FAIL)|aa.|caaco|co
[0] => aab
[1] => aac
[2] => aad
这个动词与(*SKIP)
不同,因为它不禁止使用所有先前匹配的字符,而是跳过子模式中第一个匹配的字符(或禁止子模式以此开始),如果子模式后面将失败。
a (来自"baabo"的第一个)
:第一个结果由第二个备选项找到。(aab
)
c (第一个)
:正则表达式引擎像第一种情况一样找到caac
,然后失败,但现在回溯到从"caaco"开始的第一个"a",因为第一个"c"被跳过了。
a (来自"caaco"的第一个)
:尝试第一个备选项失败,第二个成功并给出第二个结果。(aac
)
a (来自"daado"的第一个)
:第三个结果由第二个备选项找到。(aad
)