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

命名捕获组和反向引用

几乎所有现代正则表达式引擎都支持 编号捕获组编号反向引用。带有大量组和反向引用的长正则表达式可能难以阅读。它们可能特别难以维护,因为在正则表达式中间添加或删除捕获组会改变所有在添加或删除组之后组的编号。

Python 的 re 模块率先提供了解决方案:命名捕获组和命名反向引用。(?P<name>group)group的匹配项捕获到反向引用“name”中。name必须是一个字母开头的字母数字序列。group可以是任何正则表达式。你可以使用命名反向引用(?P=name)来引用组的内容。问号、P、尖括号和等号都是语法的一部分。尽管命名反向引用的语法使用圆括号,但它只是一个不执行任何捕获或分组的反向引用。HTML 标记示例可以写成<(?P<tag>[A-Z][A-Z0-9]*)\b[^>]*>.*?</(?P=tag)>

.NET也支持命名捕获。Microsoft 的开发人员发明了自己的语法,而不是遵循 Python 开创的并被 PCRE(当时唯一两个支持命名捕获的正则表达式引擎)复制的语法。(?<name>group)(?'name'group)group的匹配项捕获到反向引用“name”中。命名反向引用是\k<name>\k'name'。与 Python 相比,命名组的语法中没有 P。命名反向引用的语法更类似于编号反向引用的语法,而不是 Python 使用的语法。你可以在名称周围使用单引号或尖括号。这在正则表达式中没有任何区别。你可以交替使用这两种样式。在使用单引号分隔字符串的编程语言中,使用尖括号的语法更可取,而在将正则表达式添加到 XML 文件时,使用单引号的语法更可取,因为这最大程度地减少了必须进行的转义量,才能将正则表达式格式化为文字字符串或 XML 内容。

由于 Python 和 .NET 引入了自己的语法,因此我们将这两个变体称为命名捕获和命名反向引用的“Python 语法”和“.NET 语法”。如今,许多其他正则表达式风格都复制了此语法。

Perl 5.10 为命名的捕获和反向引用添加了对 Python 和 .NET 语法的支持。它还为命名的反向引用添加了两种语法变体:\k{one}\g{two}。Perl 中命名的反向引用的五种语法之间没有区别。所有这些都可以互换使用。在替换文本中,你可以内插变量 $+{name} 来插入由命名的捕获组匹配的文本。

PCRE 7.2 及更高版本支持 Perl 5.10 支持的所有命名捕获和反向引用语法。旧版本的 PCRE 支持 Python 语法,即使当时它不是“与 Perl 兼容”的。使用 PCRE 实现其正则表达式支持的语言,例如 PHPDelphiR 也支持所有这些语法。遗憾的是,PHP 或 R 都不支持替换文本中的命名引用。你必须对命名的组使用编号引用。PCRE 根本不支持搜索和替换。

Java 7XRegExp 复制了 .NET 语法,但只复制了带尖括号的变体。Ruby 1.9 支持 .NET 语法的两个变体。 JGsoft 风格 支持 Python 语法和 .NET 语法的两个变体。

Boost 1.42 及更高版本使用带尖括号或引号的 .NET 语法支持命名的捕获组,并使用来自 Perl 5.10 的带大括号的 \g 语法支持命名的反向引用。Boost 1.47 还支持使用带尖括号和引号的 \k 语法进行反向引用。Boost 1.47 允许这些变体相乘。Boost 1.47 允许使用 \g\k 以及大括号、尖括号或引号指定命名和编号的反向引用。因此,Boost 1.47 及更高版本在基本 \1 语法之上有六种反向引用语法的变体。这使得 Boost 与 Ruby、PCRE、PHP、R 和 JGsoft 发生冲突,后者将带尖括号或引号的 \g 视为 子例程调用

命名的捕获组的数字

不建议混合使用命名和编号的捕获组,因为各种风格在对组进行编号的方式上并不一致。如果某个组不需要名称,可以使用 (?:group) 语法使其成为非捕获组。在 .NET 中,可以通过设置 RegexOptions.ExplicitCapture 使所有未命名组成为非捕获组。在 Delphi 中,设置 roExplicitCapture。对于 XRegExp,使用 /n 标志。Perl 从 Perl 5.22 开始支持 /n。对于 PCRE,设置 PCRE_NO_AUTO_CAPTUREJGsoft 风格 和 .NET 支持 (?n) 模式修饰符。如果你使所有未命名组成为非捕获组,则可以跳过本部分,让自己省点心。

大多数风格通过从左到右计算其左括号来对命名和未命名捕获组进行编号。向现有正则表达式中添加一个命名捕获组仍然会改变未命名组的编号。然而,在 .NET 中,未命名捕获组首先被分配编号,从左到右计算其左括号,跳过所有命名组。之后,命名组被分配后续编号,从左到右计算命名组的左括号。

JGsoft 正则表达式引擎 复制了 Python 和 .NET 语法,当时只有 Python 和 PCRE 使用 Python 语法,只有 .NET 使用 .NET 语法。因此,它还复制了 Python 和 .NET 的编号行为,以便针对 Python 和 .NET 的正则表达式保持其行为。它像 Python 一样对 Python 风格的命名组与未命名组进行编号。它像 .NET 一样对 .NET 风格的命名组进行编号。即使你在同一个正则表达式中混合了这两种风格,这些规则也适用。

例如,正则表达式 (a)(?P<x>b)(c)(?P<y>d) 按预期匹配 abcd。如果你使用此正则表达式和替换 \1\2\3\4$1$2$3$4(取决于风格)进行搜索和替换,你将获得 abcd。所有四个组都从左到右按从一到四的顺序进行编号。

使用 .NET 时,情况会稍微复杂一些。正则表达式 (a)(?<x>b)(c)(?<y>d) 再次匹配 abcd。但是,如果你使用 $1$2$3$4 作为替换内容进行搜索和替换,你将获得 acbd。首先,未命名组 (a)(c) 获得了数字 1 和 2。然后,命名组“x”和“y”获得了数字 3 和 4。

在所有其他复制了 .NET 语法的风格中,正则表达式 (a)(?<x>b)(c)(?<y>d) 仍然匹配 abcd。但在所有这些风格中,除了 JGsoft 风格外,替换内容 \1\2\3\4$1$2$3$4(取决于风格)会得到 abcd。所有四个组都从左到右编号。

在使用 JGsoft 风格的 PowerGREP 中,命名的捕获组扮演着特殊角色。具有相同名称的组在同一 PowerGREP 操作中的所有正则表达式和替换文本之间共享。这允许在操作的一处捕获的命名捕获组在操作的后面部分被引用。因此,PowerGREP 完全不允许对命名的捕获组进行编号引用。在正则表达式中混合使用命名组和编号组时,编号组仍然按照 Python 和 .NET 规则进行编号,就像 JGsoft 风格始终执行的那样。

具有相同名称的多组

.NET 框架JGsoft 风格 允许正则表达式中的多个组具有相同名称。所有具有相同名称的组共享它们匹配的文本的相同存储。因此,对该名称的反向引用匹配最近捕获到内容的具有该名称的组匹配的文本。在替换文本中对该名称的引用插入最后捕获到内容的具有该名称的组匹配的文本。

PerlRuby 也允许具有相同名称的组。但这些风格仅使用障眼法来使其看起来像所有具有相同名称的组都作为一个组。实际上,这些组是分开的。在 Perl 中,反向引用匹配具有该名称且匹配到内容的最左组捕获的文本。在 Ruby 中,反向引用匹配具有该名称的任何组捕获的文本。回溯使 Ruby 尝试所有组。

因此在 Perl 和 Ruby 中,只有当组位于正则表达式中的不同备选方案中时,才能有意义地使用具有相同名称的组,以便只有具有该名称的组之一可以捕获任何文本。然后对该组的回引与该组捕获的文本合理匹配。

例如,如果你想匹配一个后跟数字 0..5 的“a”,或者一个后跟数字 4..7 的“b”,并且你只关心数字,则可以使用正则表达式 a(?<digit>[0-5])|b(?<digit>[4-7])。在这四种风格中,名为“digit”的组随后将为你提供匹配的数字 0..7,无论字母是什么。如果你希望此匹配后跟 c 和完全相同的数字,则可以使用 (?:a(?<digit>[0-5])|b(?<digit>[4-7]))c\k<digit>

PCRE 默认不允许重复命名的组。PCRE 6.7 及更高版本允许它们,如果你启用该选项或使用 模式修饰符 (?J)。但在 PCRE 8.36 之前,这并不是很有用,因为回引始终指向正则表达式中具有该名称的第一个捕获组,无论它是否参与匹配。从 PCRE 8.36(因此是 PHP 5.6.9 和 R 3.1.3)以及 PCRE2 开始,回引指向实际参与匹配的具有该名称的第一个组。尽管 PCRE 和 Perl 以相反的方向处理重复组,但如果你遵循仅在不同备选方案中使用具有相同名称的组的建议,最终结果是相同的。

Boost 允许重复命名的组。在 Boost 1.47 之前,这并不是很有用,因为回引始终指向正则表达式中回引之前出现的具有该名称的最后一个组。在 Boost 1.47 及更高版本中,回引指向实际参与匹配的具有该名称的第一个组,就像在 PCRE 8.36 及更高版本中一样。

Python、Java 和 XRegExp 3 不允许多个组使用相同的名称。这样做会产生正则表达式编译错误。XRegExp 2 允许它们,但没有正确处理它们。

在 Perl 5.10、PCRE 8.00、PHP 5.2.14 和 Boost 1.42(或这些版本的更高版本)中,当您希望不同备选方案中的组具有相同名称时,最好使用分支重置组,如 (?|a(?<digit>[0-5])|b(?<digit>[4-7]))c\k<digit>。使用此特殊语法(使用 (?| 而不是 (?: 打开组),名为“digit”的两个组实际上是同一个组。然后,对该组的回引始终在这些风格之间得到正确而一致的处理。(PCRE 和 PHP 的旧版本可能支持分支重置组,但无法正确处理分支重置组中的重复名称。)