嵌套捕获组如何在正则表达式中编号?

Posted

技术标签:

【中文标题】嵌套捕获组如何在正则表达式中编号?【英文标题】:How are nested capturing groups numbered in regular expressions? 【发布时间】:2010-11-21 18:51:11 【问题描述】:

对于正则表达式应如何处理嵌套括号的捕获行为,是否有定义的行为?更具体地说,您是否可以合理地期望不同的引擎会在第一个位置捕获外括号,而在后续位置捕获嵌套括号?

考虑以下 php 代码(使用 PCRE 正则表达式)

<?php
  $test_string = 'I want to test sub patterns';
  preg_match('(I (want) (to) test) sub (patterns)', $test_string, $matches);
  print_r($matches);
?>

Array
(
    [0] => I want to test sub patterns  //entire pattern
    [1] => I want to test           //entire outer parenthesis
    [2] => want             //first inner
    [3] => to               //second inner
    [4] => patterns             //next parentheses set
)

首先捕获整个带括号的表达式(我想测试),然后再捕获内部带括号的模式(“want”和“to”)。这是合乎逻辑的,但我可以看到同样合乎逻辑的情况是先捕获子括号,然后再捕获整个模式。

那么,这是“首先捕获整个事物”在正则表达式引擎中定义的行为,还是取决于模式的上下文和/或引擎的行为(PCRE 不同于 C# Java 与等不同)?

【问题讨论】:

如果您真的对所有正则表达式风格感兴趣,那么“语言不可知”标签就是您想要的。有太多的风格无法一一列举,而且大多数都不符合任何真正的标准(尽管它们在捕获组编号方面非常一致)。 可以使用$1、$2、$3....等方式访问该组。如何进入第十组?会是10美元吗?我不认为 10 美元会起作用,因为它将被解释为 1 美元后跟 0。这是否意味着我们最多只能有 9 个组?如果作者可以,请将此作为问题的一部分,那么这将是一个了解正则表达式中嵌套组的所有信息的地方。 【参考方案1】:

在我工作过的所有平台上,按左括号的顺序捕获的顺序是标准的。(perl、php、ruby、egrep)

【讨论】:

"按左括号的顺序捕获" 谢谢,这是描述行为的更简洁的方式。 您可以在 Perl 5.10 和 Perl 6 中重新编号。【参考方案2】:

来自perlrequick

如果正则表达式中的分组是 嵌套,$1 获取具有 最左边的左括号,$2 下一个左括号等。

警告:排除非捕获组左括号 (?=)

更新

我不太使用 PCRE,因为我通常使用真实的东西 ;),但 PCRE's docs 显示与 Perl 相同:

子模式

2. 将子模式设置为捕获子模式。这意味着,当整个模式匹配时,与子模式匹配的那部分主题字符串通过pcre_exec()ovector 参数传递回调用者。左括号从左到右(从 1 开始)计数,以获得捕获子模式的数量。

例如,如果字符串“the red king”与模式匹配

the ((red|white) (king|queen))

捕获的子串是“red king”、“red”和“king”,分别编号为 1、2 和 3。

如果 PCRE 正在偏离 Perl 正则表达式的兼容性,也许应该重新定义首字母缩略词——“Perl Cognate Regular Expressions”、“Perl Comparable Regular Expressions”之类的。或者只是剥离意义的字母。

【讨论】:

@Sinan :他在 PHP 中使用 PCRE,即“Perl-Compatible Regular Expressions”;所以应该和直接使用Perl完全一样 Pascal,PCRE 最初是作为 Perl 兼容的正则表达式集的尝试,但近年来两者略有不同。仍然非常相似,但高级功能集存在细微差别。 (另外,根据问题,我对所有平台都感兴趣) 实际上,这些天大部分“漂流”的是 Perl,但您是对的:“Perl 兼容”正在迅速从用词不当变为不合逻辑。 :D @Alan,Perl 肯定在移动。 P5.10 改变了一些东西,但 6 将大不相同。 P 几乎肯定需要被解释为“Perl 5”。 PCRE 是一个很棒的项目,我怎么称赞都不为过,它是许多项目的天赐之物。 我在第一个引号 Caveat 下添加了这个:排除非捕获组左括号 (?=)。当我编辑它时,我没有意识到我没有登录。只有当我添加此评论时,我才被提示输入凭据。所以,它现在需要另外 1 人批准!【参考方案3】:

我知道的每种正则表达式风格都按左括号出现的顺序分组。外部组在其包含的子组之前编号只是自然结果,而不是明确的政策。

有趣的是命名组。在大多数情况下,它们遵循相同的按括号的相对位置编号的策略——名称只是数字的别名。但是,在 .NET 正则表达式中,命名组与编号组分开编号。例如:

Regex.Replace(@"one two three four", 
              @"(?<one>\w+) (\w+) (?<three>\w+) (\w+)",
              @"$1 $2 $3 $4")

// result: "two four one three"

实际上,numbername 的别名;分配给命名组的编号从“真实”编号组离开的位置开始。这似乎是一个奇怪的策略,但有一个很好的理由:在 .NET 正则表达式中,您可以在一个正则表达式中多次使用相同的组名。这使得像 this thread 中的正则表达式一样可以用于匹配来自不同语言环境的浮点数:

^[+-]?[0-9]1,3
(?:
    (?:(?<thousand>\,)[0-9]3)*
    (?:(?<decimal>\.)[0-9]2)?
|
    (?:(?<thousand>\.)[0-9]3)*
    (?:(?<decimal>\,)[0-9]2)?
|
    [0-9]*
    (?:(?<decimal>[\.\,])[0-9]2)?
)$

如果有千位分隔符,无论正则表达式的哪个部分匹配它,它都会保存在“千”组中。同样,小数分隔符(如果有的话)将始终保存在“十进制”组中。当然,有一些方法可以在没有可重用命名组的情况下识别和提取分隔符,但是这种方法要方便得多,我认为它不仅证明了奇怪的编号方案是合理的。

然后是 Perl 5.10+,它让我们对捕获组的控制比我知道的要多。 :D

【讨论】:

【参考方案4】:

是的,这对您感兴趣的所有语言都进行了很好的定义:

Java - http://java.sun.com/javase/6/docs/api/java/util/regex/Pattern.html#cg “捕获组通过从左到右计算其左括号来编号。...组零始终代表整个表达式。” .Net - http://msdn.microsoft.com/en-us/library/bs2twtah(VS.71).aspx “使用 () 的捕获会根据左括号的顺序自动编号,从一个开始。第一个捕获,捕获元素编号为零,是与整个正则表达式模式匹配的文本。”) PHP(PCRE 函数) - http://www.php.net/manual/en/function.preg-replace.php#function.preg-replace.parameters "\0或$0指的是整个模式匹配的文本。左括号从左到右(从1开始)计数,得到捕获子模式的个数。" (弃用的 POSIX 函数也是如此)

PCRE - http://www.pcre.org/pcre.txt 要补充 Alan M 所说的内容,请搜索“pcre_exec() 如何返回捕获的子字符串”并阅读以下第五段:

第一对整数 ovector[0] 和 ovector[1] 标识 与整个模式匹配的主题字符串的一部分。下一个 pair 用于第一个捕获子模式,依此类推。价值 pcre_exec() 返回的比最高编号的对多一个 已经设置好了。例如,如果已捕获两个子字符串,则 返回值为 3。如果没有捕获子模式,则返回 成功匹配的值为 1,表示只有第一对 已设置偏移量。 Perl 的不同 - http://perldoc.perl.org/perlre.html#Capture-buffers $1、$2 等匹配捕获组,如您所料(即通过出现左括号),但是 $0 返回程序名称,而不是整个查询字符串 - 让您使用 $& 代替。

您很可能会发现其他语言(Python、Ruby 等)的类似结果。

您说首先列出内部捕获组同样合乎逻辑,您是对的 - 这只是在关闭而不是打开括号时建立索引的问题。 (如果我理解正确的话)。但是这样做不太自然(例如,它不遵循阅读方向约定),因此通过检查来确定哪个捕获组将在给定的结果索引处变得更加困难(可能并不显着)。

将整个匹配字符串放在位置 0 也很有意义 - 主要是为了保持一致性。它允许整个匹配的字符串保持在相同的索引处,无论从正则表达式到正则表达式的捕获组数量如何,也不管实际匹配任何内容的捕获组数量如何(例如,Java 将折叠每个捕获的匹配组数组的长度组与任何内容都不匹配(例如,例如“a (.*)pattern”)。您始终可以检查 capture_group_results[capturing_group_results_length - 2],但这并不能很好地转换为动态创建变量的 Perl 语言($1 , $2 等)(Perl 当然是一个不好的例子,因为它使用 $& 作为匹配的表达式,但你明白了:)。

【讨论】:

不错的答案.. 但是如何更新 Python(2 和 3)呢 :-) javascript 怎么样!?!

以上是关于嵌套捕获组如何在正则表达式中编号?的主要内容,如果未能解决你的问题,请参考以下文章

Java 正则表达式之捕获组

正则表达式的$

正则基础之——捕获组(capture group)

Perl 中的正则表达式组:如何从正则表达式组中捕获与字符串中出现的未知数量/多个/变量匹配的元素到数组中?

正则表达式:如何在捕获单个组时匹配整个字符串 [重复]

正则表达式中 如何取出所有组中的值?