不同Ruby版本的`scan`和`match`行为存在差异

时间:2017-03-24 19:46:04

标签: ruby regex match

背景

这个问题涉及Ruby中String#scanString#match方法的行为。我正在使用一个递归正则表达式,它应该匹配一对平衡的大括号。您可以在以下位置查看此正则表达式/(\((?:[^\(\)]*\g<0>*)*\))/https://regex101.com/r/Q1lOC8/1。它显示了预期的行为:匹配具有平衡嵌套括号集的顶级括号集。一些说明问题的示例代码如下:

➜  cat test.rb                                                                          
s = "1+(x*(3-4)+5)-1"
r = /(\((?:[^\(\)]*\g<0>*)*\))/
puts s.match(r).inspect
puts s.scan(r).inspect

问题

在ruby-2.3.3和ruby-2.4.1中运行上面的示例代码时,我得到了不同的结果:

➜  docker run --rm -v "$PWD":/usr/src/app -w /usr/src/app ruby:2.3.3-alpine ruby test.rb
#<MatchData "(x*(3-4)+5)" 1:")">
[[")"]]
➜  docker run --rm -v "$PWD":/usr/src/app -w /usr/src/app ruby:2.4.1-alpine ruby test.rb
#<MatchData "(x*(3-4)+5)" 1:"(x*(3-4)+5)">
[["(x*(3-4)+5)"]]

ruby​​-2.4.1中的案例正是我所期待的。 match在两种情况下(x*(3-4)+5)正确匹配相同的外部括号集,但在ruby-2.3.3中,由于某种原因,第一组匹配仅为")"。如果我将正则表达式更改为/(\(.*\))/,则两个版本的行为相同(与上面的2.4.1相同),但它不再确保嵌套的括号是平衡的。

在这种情况下match的真实预期行为是什么?

1 个答案:

答案 0 :(得分:1)

首先,我应该注意,在regex101.com上运行的功能无需在任何地方工作:在目标环境中必须在在线正则表达式测试器的帮助下编写的任何正则表达式。您使用PCRE选项进行了测试,并且它有效,因为PCRE是与Ruby中使用的Onigmo不同的库。

现在,问题似乎是Onigmo正则表达式引擎如何处理2.3.3中的递归:\g<0>构造递归整个模式(第0组),外部捕获括号(组1)也重复(虽然它的ID保持不变),但有效地创建了一个重复捕获组。这些组中的值在每次迭代时都会被重写,这就是最终得到)的原因。

解决方法是递归第1组子模式以保持第1组值完整而不在每次迭代时重写其值(因为在模式中定义了捕获组,String#scan仅返回捕捉(S))。

使用

r = /(\((?:[^\(\)]*\g<1>*)*\))/
                      ^