快速入门
教程
工具和语言
示例
参考
书评
正则表达式教程
简介
目录
特殊字符
不可打印字符
正则表达式引擎内部
字符类
字符类减法
字符类交集
简写字符类
锚点
单词边界
交替
可选项
重复
分组和捕获
反向引用
反向引用,第 2 部分
命名组
相对反向引用
分支重置组
自由间距和注释
Unicode
模式修饰符
原子分组
独占量词
前瞻和后顾
环视,第 2 部分
将文本排除在匹配之外
条件
平衡组
递归
子例程
无限递归
递归和量词
递归和捕获
递归和反向引用
递归和回溯
POSIX 方括号表达式
零长度匹配
继续匹配
本网站上的更多内容
简介
正则表达式快速入门
正则表达式教程
替换字符串教程
应用程序和语言
正则表达式示例
正则表达式参考
替换字符串参考
书评
可打印 PDF
关于本网站
RSS 源和博客
RegexBuddy—Better than a regular expression tutorial!

正则表达式中的如果-那么-否则条件

特殊结构 (?ifthen|else) 允许你创建条件正则表达式。如果如果部分评估为真,则正则表达式引擎将尝试匹配那么部分。否则,将尝试否则部分。语法由一对括号组成。左括号后必须紧跟一个问号,紧跟如果部分,紧跟那么部分。此部分后可以跟一个竖线和否则部分。你可以省略否则部分,以及竖线。

对于if部分,可以使用前瞻和后顾构造。使用正前瞻,语法变为(?(?=regex)then|else)。因为前瞻有自己的括号,所以ifthen部分清晰地分开了。

记住前瞻构造不消耗任何字符。如果你使用前瞻作为if部分,那么正则表达式引擎将尝试在if尝试的位置匹配thenelse部分(取决于前瞻的结果)。

或者,你可以在if部分检查捕获组是否参与了到目前为止的匹配。将捕获组的编号放在括号内,并将其用作if部分。请注意,虽然对反向引用的条件检查的语法与捕获组中的编号相同,但不会创建捕获组。该编号和括号是if-then-else语法的一部分,该语法以(?开头。

对于thenelse,可以使用任何正则表达式。如果你想使用交替,则必须使用括号thenelse组合在一起,如(?(?=condition)(then1|then2|then3)|(else1|else2|else3))。否则,无需在thenelse部分周围使用括号。

查看正则表达式引擎内部

正则表达式(a)?b(?(1)c|d)由可选捕获组(a)?、字面量b和测试捕获组的条件(?(1)c|d)组成。此正则表达式匹配bdabc。它不匹配bc,但在abd中匹配bd。让我们看看此正则表达式如何对这四个主题字符串中的每一个起作用。

当应用于 bd 时,a 无法匹配。由于包含 a 的捕获组是可选的,引擎继续在主题字符串的开头使用 b。由于整个组是可选的,因此该组不参与匹配。任何后续的 反向引用(如 \1)都将失败。请注意,(a)?(a?) 有很大不同。在前面的正则表达式中,如果 a 失败,则捕获组不参与匹配,并且对该组的反向引用将失败。在后面的组中,捕获组始终参与匹配,捕获 a 或不捕获任何内容。对参与匹配且未捕获任何内容的捕获组的反向引用始终成功。评估此类组的条件会执行“then”部分。简而言之:如果您想在条件中使用对组的引用,请使用 (a)? 而不是 (a?)

继续我们的正则表达式,b 匹配 b。正则表达式引擎现在评估条件。第一个捕获组根本不参与匹配,因此尝试“else”部分或 dd 匹配 d,并且找到了整体匹配。

继续我们的第二个主题字符串 abca 匹配 a,该匹配由捕获组捕获。随后,b 匹配 b。正则表达式引擎再次评估条件。捕获组参与了匹配,因此尝试“then”部分或 cc 匹配 c,并且找到了整体匹配。

我们的第三个主题 bc 并未以 a 开头,因此捕获组不会参与匹配尝试,就像我们在第一个主题字符串中看到的那样。 b 仍然匹配 b,并且引擎继续进行条件判断。第一个捕获组根本没有参与匹配,因此尝试“else”部分或 dd 不匹配 c,并且在字符串开头进行的匹配尝试失败。引擎确实尝试从字符串中的第二个字符开始再次尝试,但由于 b 不匹配 c 而失败。

第四个主题 abd 是最有趣的。与第二个字符串一样,捕获组获取 a,并且 b 匹配。捕获组参与了匹配,因此尝试“then”部分或 cc 无法匹配 d,并且匹配尝试失败。请注意,此时不会尝试“else”部分。捕获组参与了匹配,因此仅使用“then”部分。但是,正则表达式引擎尚未完成。它从开头重新启动正则表达式,在主题字符串中向前移动一个字符。

从字符串中的第二个字符开始,a 无法匹配 b。捕获组不会参与从字符串中的第二个字符开始的第二次匹配尝试。正则表达式引擎会越过可选组,并尝试匹配 b,后者匹配成功。正则表达式引擎现在到达正则表达式中的条件,以及主题字符串中的第三个字符。第一个捕获组不会参与当前匹配尝试,因此会尝试“else”部分或 dd 匹配 d,并且找到一个整体匹配 bd

如果您想避免这个最后的匹配结果,您需要使用 锚点^(a)?b(?(1)c|d)$ 在最后一个主题字符串中找不到任何匹配项。插入符号无法匹配字符串中的第二个和第三个字符之前。

命名和相对条件

条件受 JGsoft 引擎PerlPCREPython.NET 支持。Ruby 从 2.0 版开始支持它们。基于 PCRE 的正则表达式特性的语言(例如 DelphiPHPR)也支持条件。

所有这些风格还支持命名的捕获组。您可以使用捕获组的名称而不是其数字作为if测试。不同正则表达式风格的语法略有不同。在 Python、.NET 和 JGsoft 应用程序中,您只需在括号中指定组的名称。 (?<test>a)?b(?(test)c|d) 是上一部分中使用命名捕获的正则表达式。在 Perl 或 Ruby 中,您必须在组的名称周围加上尖括号或引号,并将其放在条件的括号内:(?<test>a)?b(?(<test>)c|d)(?'test'a)?b(?('test')c|d)。PCRE 支持所有这三种变体。

PCRE 7.2 及更高版本和 JGsoft V2 还支持相对条件。其语法与引用带编号捕获组的条件的语法相同,只是在组号前添加了加号或减号。然后,条件从打开条件的 (?( 开始,向左(减号)或向右(加号)计算左括号的个数。 (a)?b(?(-1)c|d) 是编写上述正则表达式的另一种方法。好处是,如果您在正则表达式的开头或结尾添加捕获组,此正则表达式也不会中断。

Python 支持使用编号或命名捕获组的条件。Python 不支持使用环视的条件,即使 Python 在条件外部支持环视。对于类似 (?(?=regex)then|else) 的条件,您可以交替使用两个相反的环视:(?=regex)then|(?!regex)else

引用不存在的捕获组的条件

BoostRuby 将引用不存在的捕获组的条件视为错误。本教程中讨论的所有其他版本的最新版本则不会。它们只是让此类条件始终尝试“else”部分。不过,一些版本改变了它们的看法。Python 3.4 及更早版本和 PCRE 7.6 及更早版本(因此 PHP 5.2.5 及更早版本)过去将它们视为错误。

示例:提取电子邮件标头

正则表达式 ^((From|To)|Subject)((?(2)\w+@\w+\.[a-z]+|.+)) 从电子邮件消息中提取发件人、收件人和主题标头。标头名称被捕获到第一个反向引用中。如果标头是发件人或收件人标头,它也会被捕获到第二个反向引用中。

该模式的第二部分是 if-then-else 条件 (?(2)\w+@\w+\.[a-z]+|.+))if 部分检查第二个捕获组是否参与了到目前为止的匹配。如果标头是发件人或收件人标头,它将参与。在这种情况下,条件的then 部分 \w+@\w+\.[a-z]+ 尝试匹配电子邮件地址。为了使示例简单,我们使用一个过于简单的正则表达式来匹配电子邮件地址,并且我们不会尝试匹配通常也是发件人或收件人标头一部分的显示名称。

如果第二个捕获组到目前为止没有参与匹配,则尝试else 部分 .+。这只是匹配行的其余部分,允许任何测试主题。

最后,我们在条件周围放置一对额外的括号。这将条件匹配的电子邮件标头的内容捕获到第三个反向引用中。条件本身不会捕获任何内容。在实现此正则表达式时,第一个捕获组将存储标头名称(“发件人”、“收件人”或“主题”),第三个捕获组将存储标头值。

您可以尝试在“else”部分中放入另一个条件来匹配更多标题。例如,^((发件人|收件人)|(日期)|主题)((?(2)\w+@\w+\.[a-z]+|(?(3)mm/dd/yyyy|.+))) 将匹配“发件人”、“收件人”、“日期”或“主题”,并使用正则表达式 mm/dd/yyyy 来检查日期是否有效。显然,日期验证正则表达式只是一个虚拟的例子,以保持示例的简单性。标题在第一组中被捕获,其经过验证的内容在第四组中被捕获。

如您所见,使用条件的正则表达式很快变得难以处理。我建议您仅在工具只允许您使用一个正则表达式时使用它们。在编程时,您最好使用正则表达式 ^(发件人|收件人|日期|主题)(.+) 来捕获一个标题及其未验证的内容。在您的源代码中,检查第一个捕获组中返回的标题的名称,然后使用第二个正则表达式来验证第一个正则表达式的第二个捕获组中返回的标题的内容。虽然您必须编写几行额外的代码,但此代码将更容易理解和维护。如果您预编译所有正则表达式,使用多个正则表达式将与一个包含条件的大正则表达式一样快,甚至更快。