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

2022-08-31 10:05:58

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

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

perlrequick

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

注意事项:排除非捕获组左括号 (?=)

更新

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

子模式

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

例如,如果字符串“红色之王”与模式匹配

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

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

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


答案 2

是的,这几乎是针对您感兴趣的所有语言的良好定义:

  • 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
    “使用 () 捕获根据左括号的顺序自动编号,从 1 开始。第一个捕获,捕获元素编号 0,是与整个正则表达式模式匹配的文本。
  • 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 返回程序名称,而不是整个查询字符串 - 以获得您使用 $& 代替。

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

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

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