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

正则表达式递归

Perl 5.10PCRE 4.0Ruby 2.0以及这三个语言的所有更高版本支持正则表达式递归。Perl 使用语法 (?R),其中 (?0) 为同义词。Ruby 2.0 使用 \g<0>。PCRE 从版本 7.7 开始支持所有三个语法。早期版本仅支持 Perl 语法(Perl 实际上从 PCRE 复制了该语法)。DelphiPHPR的最新版本也支持所有三个语法,因为它们的正则表达式函数基于 PCRE。 JGsoft V2还支持所有正则表达式递归变体。

虽然 Ruby 1.9 没有用于正则表达式递归的任何语法,但它支持 捕获组递归。因此,如果你将整个正则表达式包装在捕获组中,则可以在 Ruby 1.9 中递归整个正则表达式。 .NET 不支持递归,但它支持 平衡组,这些组可用于代替递归来匹配平衡结构。

正如我们稍后将看到的,Perl、PCRE 和 Ruby 在递归期间处理 反向引用回溯 的方式有所不同。虽然它们复制了彼此的语法,但它们并没有复制彼此的行为。然而,JGsoft V2 复制了它们的语法和行为。因此,JGsoft V2 有三种不同的方法来执行正则表达式递归,你可以通过使用不同的语法来选择。但这些差异不会在这一页的基本示例中发挥作用。

Boost 1.42 从 Perl 复制了语法。但它的实现因错误而受到损害。Boost 1.60 尝试修复 递归上的量词 的行为,但它仍然与其他版本有很大不同,并且与 Boost 的以前版本不兼容。Boost 1.64 终于停止在 无限递归 时崩溃。但整个正则表达式的递归仍然只尝试第一个备选方案。

简单递归

正则表达式 a(?R)?za(?0)?za\g<0>?z 都匹配一个或多个字母 a,后跟相同数量的字母 z。由于这些正则表达式在功能上是相同的,因此我们将使用具有 R 的递归语法来查看此正则表达式如何匹配字符串 aaazzz

首先,a 匹配字符串中的第一个 a。然后,正则表达式引擎到达 (?R)。这告诉引擎在字符串中的当前位置再次尝试整个正则表达式。现在,a 匹配字符串中的第二个 a。引擎再次到达 (?R)。在第二次递归中,a 匹配第三个 a。在第三次递归中,a 无法匹配字符串中的第一个 z。这导致 (?R) 失败。但正则表达式使用量词使 (?R) 可选。因此,引擎继续使用 z,它匹配字符串中的第一个 z

现在,正则表达式引擎已到达正则表达式的末尾。但由于它在递归中深度为两层,因此尚未找到总体匹配项。它只找到了 (?R) 的匹配项。在成功匹配后退出递归,引擎也到达 z。它现在匹配字符串中的第二个 z。引擎在递归中仍深度为一层,它通过成功匹配退出递归。最后,z 匹配字符串中的第三个 z。引擎再次位于正则表达式的末尾。这一次,它不在任何递归中。因此,它返回 aaazzz 作为总体正则表达式匹配项。

匹配平衡结构

递归的主要目的是匹配平衡结构或嵌套结构。通用正则表达式为 b(?:m|(?R))*e,其中 b 是结构的开头,m 是结构中间可能出现的内容,e 是结构末尾可能出现的内容。为了得到正确的结果,bme 中的任何两个都不应该能够匹配相同的文本。你可以使用 原子组 而不是 非捕获组 来提高性能:b(?>m|(?R))*e

一个常见的实际用途是匹配一组平衡的括号。\((?>[^()]|(?R))*\) 匹配一对括号,括号中可以包含任何文本,包括无限数量的括号,只要它们都正确配对即可。如果主题字符串包含不平衡的括号,则第一个正则表达式匹配项是最左边的平衡括号对,它可能出现在不平衡的左括号之后。如果你想要一个在包含不平衡括号的字符串中找不到任何匹配项的正则表达式,那么你需要使用 子例程调用 而不是递归。如果你想将多个平衡括号对的序列作为单个匹配项找到,那么你也需要一个子例程调用。

带交替的递归

如果平衡结构中间可能出现的内容也可以在没有开头和结尾部分的情况下单独出现,那么通用正则表达式为 b(?R)*e|m。同样,bme 都需要互斥。\((?R)*\)|[^()]+ 匹配一对平衡括号,就像上一节中的正则表达式一样。但它也匹配任何不包含任何括号的文本。

此正则表达式在 Boost 中无法正常工作。如果正则表达式中包含未在组内的 交替,则 Boost 中整个正则表达式的递归仅尝试第一个备选。因此,Boost 中的 \((?R)*\)|[^()]+ 匹配任意数量的平衡括号,这些括号可以任意深度嵌套,中间没有文本,或者匹配任何不包含任何括号的文本。如果您翻转备选,则 Boost 中的 [^()]+|\((?R)*\) 匹配不包含任何括号的任何文本,或匹配一对括号,括号之间不包含任何文本。在所有其他版本中,这两个正则表达式找到相同的匹配项。

针对 Boost 的解决方案是将交替放入组中。 (?:\((?R)*\)|[^()]+)(?:[^()]+|\((?R)*\)) 在本教程中讨论的所有支持递归的版本中找到相同的匹配项。