在正则expression式中如何嵌套捕获组?
是否有正则expression式应该如何处理嵌套圆括号的捕获行为的定义行为? 更具体地说,你是否可以合理地预期,不同的引擎会在第一个位置捕获外部括号,并在随后的位置嵌套括号?
考虑下面的PHP代码(使用PCRE正则expression式)
<?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 )
整个括号expression式首先被捕获(我想testing),接下来捕获内部括号的模式(“想要”和“到”)。 这是合乎逻辑的,但是我可以看到一个同样合理的情况是首先捕获子括号,然后捕获整个模式。
那么,这是在正则expression式引擎中定义的行为,还是取决于模式的上下文和/或引擎的行为(PCRE不同于C#的不同于Java的不同比等)?
从perlrequick
如果正则expression式中的分组是嵌套的,则$ 1得到最左边的开括号,下一个开括号的$ 2等。
更新
我不会使用PCRE,因为我通常使用真实的东西;),但是PCRE的文档与Perl相同:
子模式
2.
它将子模式设置为捕获子模式。 这意味着,当整个模式匹配时,与子模式相匹配的主体string部分将通过pcre_exec()
的ovector
parameter passing给调用者。 从左向右数(从1开始)开始括号,以获取拍摄子模式的编号。例如,如果string“红色国王”是匹配的模式
the ((red|white) (king|queen))
被捕获的子串分别是“红色王”,“红色”和“王”,分别编号为1,2和3。
如果PCRE与Perl正则expression式兼容,也许应该重新定义首字母缩略词 – “Perl同源正则expression式”,“Perl可比正则expression式”等等。 或者只是放弃意义的字母。
是的,对于您感兴趣的所有语言来说,这一切都非常明确:
- Java – http://java.sun.com/javase/6/docs/api/java/util/regex/Pattern.html#cg
“捕捉组通过从左到右数开括号来进行编号……组0总是代表整个expression式。 - .Net – http://msdn.microsoft.com/en-us/library/bs2twtah(VS.71).aspx
“使用()的捕获是根据左括号的顺序自动编号的,从第一个捕获,捕获元素编号为0,是整个正则expression式模式匹配的文本。”) - 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所说的内容中,请search“pcre_exec()如何返回捕获的子string”并阅读以下第五段:第一对整数ovector [0]和ovector [1]标识 主题string的一部分与整个模式相匹配。 下一个 对用于第一个捕获子模式,依此类推。 价值 由pcre_exec()返回的是比最高编号对多一个 已经设置好了。 例如,如果有两个子string被捕获, 返回值是3.如果没有捕获子模式,则返回 成功的匹配值是1,表明只是第一对 的偏移量已经确定。
- Perl的不同 – http://perldoc.perl.org/perlre.html#Capture-buffers
$ 1,$ 2等匹配捕获组,如你所期望的(即通过出现的括号),但$ 0返回程序名称,而不是整个查询string – 以获得您使用$&而不是。
对于其他语言(Python,Ruby和其他语言),您可能会find类似的结果。
你说,首先列出内部捕获组合是合乎逻辑的,而且你是对的 – 这只是closures索引而不是打开parens的问题。 (如果我理解正确)。 这样做虽然不那么自然(例如,它不遵循阅读方向惯例),所以通过检查确定哪个捕获组将处于给定的结果索引更困难(可能不显着)。
把整个匹配string放在位置0也是有意义的 – 主要是为了一致性。 它允许整个匹配的string保持在相同的索引,无论从正则expression式到正则expression式的数量捕获组是什么,也不pipe实际上匹配任何东西的捕获组的数量(例如,Java会为每个捕获折叠匹配的组数组的长度你可以总是检查capture_group_results [capturing_group_results_length – 2],但是这并不能很好地转换成Perl语言,它会dynamic地创buildvariables($ 1 ,$ 2等)(当然,Perl是一个不好的例子,因为它使用$&来匹配expression式,但是你明白了:)。
每一个正则expression式的风味,我知道数字按开头括号出现的顺序分组。 外部群体在被包含的子群体之前被编号只是一个自然的结果,而不是明确的政策。
有趣的地方是命名组 。 在大多数情况下,他们遵循相同的编号方式,通过相对位置的相对位置 – 名称只是数字的别名。 但是,在.NET正则expression式中,已命名的组与编号组分别编号。 例如:
Regex.Replace(@"one two three four", @"(?<one>\w+) (\w+) (?<three>\w+) (\w+)", @"$1 $2 $3 $4") // result: "two four one three"
实际上,该号码是该名称的别名; 分配给命名组的编号从“真实”编号组离开的地方开始。 这似乎是一个奇怪的政策,但有一个很好的理由:在.NET正则expression式中,您可以在正则expression式中多次使用相同的组名称。 这使得可能的正则expression式像来自这个线程的匹配来自不同语言环境的浮点数:
^[+-]?[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})? )$
如果有千位分隔符,则无论正则expression式的哪一部分匹配,都将保存在“千位”组中。 同样,小数分隔符(如果有的话)将始终保存在“decimal”组中。 当然,有些方法可以识别和提取没有可重复使用的命名组的分隔符,但是这种方式更加方便,我认为这不是奇怪的编号scheme。
然后是Perl 5.10+,这使我们能够更好地控制捕获组,这比我知道该怎么做。 :d
按照左paren的顺序捕获的顺序是我工作过的所有平台的标准(perl,php,ruby,egrep)