it-swarm.dev

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

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

考虑以下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
)

首先捕获整个带括号的表达式(我想测试),然后接下来捕获内部带括号的模式(“想要”和“到”)。这具有逻辑意义,但我可以看到一个同样合乎逻辑的情况,首先捕获子括号,然后捕获整个模式。

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

68
Alan Storm

来自 perlrequick

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

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

更新

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

子模式

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

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

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

捕获的子串是“红色王”,“红色”和“王”,分别编号为1,2和3。

如果PCRE偏离Perl正则表达式兼容性,也许应该重新定义首字母缩略词 - “Perl同源正则表达式”,“Perl Comparable Regular Expressions”或其他东西。或者只是剥离意义的字母。

53
daotoad

是的,对于你感兴趣的所有语言来说,这都是非常好的定义:

  • Java - http://Java.Sun.com/javase/6/docs/api/Java/util/regex/Pattern.html#cg
    “捕获群体的编号是从左到右计算它们的左括号。..群组0总是代表整个表达。”
  • .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],识别整个模式匹配的主题字符串的
    部分。下一个
    对用于第一个捕获子模式,依此类推。 pcre_exec()返回的值
    比已设置
    的最高编号对多一个。例如,如果已捕获两个子字符串,则
    返回值为3.如果没有捕获子模式,则成功匹配的返回
    值为1,表示只有第一对
    补偿已经确定。[。_____。]
  • Perl的不同 - http://perldoc.Perl.org/perlre.html#Capture-buffers
    $ 1,$ 2等按照您的预期匹配捕获组(即通过出现开括号),但$ 0返回程序名称,而不是整个查询字符串 - 以获得您使用$&而不是。

您很可能会为其他语言(Python,Ruby和其他语言)找到类似的结果。

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

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

16
Alan Donnelly

每个正则表达式的味道,我知道数字组按开头括号出现的顺序。那些外部群体在其所包含的子群体只是一个自然结果而不是明确的政策之前就被编号了。

有趣的地方是 命名组 。在大多数情况下,他们遵循相同的编号政策 - 这些名称只是该编号的别名。但是,在.NET正则表达式中,命名组与编号组分开编号。例如:

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

// result: "two four one three"

实际上, number name 的别名;分配给命名组的编号从“实际”编号组留下的位置开始。这似乎是一个奇怪的政策,但有一个很好的理由:在.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

8
Alan Moore

按照左边的paren顺序捕获的顺序是我工作过的所有平台的标准。(Perl,php,Ruby,egrep)

4
Devin Ceartas