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

时间:2009-08-21 19:54:54

标签: java .net regex perl language-agnostic

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

考虑以下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与...等不同?)

4 个答案:

答案 0 :(得分:52)

来自perlrequick

  

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

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

<强>更新

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

  

子模式

     

     

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

     

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

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

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

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

答案 1 :(得分:16)

是的,对于您感兴趣的所有语言来说,这几乎都很明确:

  • 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()返回捕获的子字符串”并阅读下面的第五段:

    The  first  pair  of  integers, ovector[0] and ovector[1], identify the
    portion of the subject string matched by the entire pattern.  The next
    pair  is  used for the first capturing subpattern, and so on. The value
    returned by pcre_exec() is one more than the highest numbered pair that
    has  been  set.  For example, if two substrings have been captured, the
    returned value is 3. If there are no capturing subpatterns, the  return
    value from a successful match is 1, indicating that just the first pair
    of offsets has been set.
    
  • Perl的不同 - http://perldoc.perl.org/perlre.html#Capture-buffers
    $ 1,$ 2等匹配捕获组,如你期望的那样(即通过出现开括号),但$ 0返回程序名称,而不是整个查询字符串 - 得到你使用$&amp;代替。

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

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

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

答案 2 :(得分:8)

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

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

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

// result: "two four one three"

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

答案 3 :(得分:4)

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