Ruby gsub正则表达式出乎意料的行为

时间:2016-06-02 13:25:03

标签: ruby regex

我以为我很了解正则表达式,但这令我感到困惑:

irb(main):016:0> source = "/foo/bar"
=> "/foo/bar"
irb(main):017:0> source.gsub( /[^\/]*\Z/, "fubar" )
=> "/foo/fubarfubar"

据我所知,/[^\/]*\Z/有一个与bar匹配的唯一展开式广告,因此应该会生成/foo/fubar。我完全看不出为什么我会将fubarfubar作为替代品。

如果我拨打sub而不是gsub,则替换有效,所以这不是解决问题的问题,而是揭示我对gsub的误解。

2 个答案:

答案 0 :(得分:5)

您需要使用sub,因为您只需要在字符串末尾替换一次:

source.sub( /[^\/]*\Z/, "fubar" )
       ^^^

请参阅IDEONE demo

问题很可能与收集匹配的方式有关,并且由于您模式匹配一​​个空字符串,尽管最后,最后一个 null 也可以被视为第二个匹配。它不仅是一个Ruby问题,许多其他语言中也存在类似的错误。

所以,实际上,这就是发生的事情:

  • [^\/]*\Z模式匹配bar,并将其替换为foobar
  • 正则表达式索引位于字符串的末尾 - 是的,有一个NULL,但Ruby仍然认为它是一个有效的"字符串"处理和
  • [^\/]*\Z匹配NULL,并添加另一个foobar

如果您需要使用gsub,请替换*量词,该量词允许匹配0个字符与+,需要至少出现1次量化子模式,避免匹配0长度字符串:

source.gsub( /[^\/]+\Z/, "fubar" )
                   ^

经验法则:避免在Regex替换方法中匹配空字符串的regexp!

答案 1 :(得分:2)

我根本不认为这是一个错误。正则表达式可以并且将匹配零宽度位置。

因此,正则表达式引擎更像这样看到字符串"xox"

"" "x" "" "o" "" "x" ""

(有趣的事实:在Ruby中,上面的实际结果是"xox"

如果我们gsub一个x _,那么一切都按预期工作:

"xox".gsub(/x/, "_") #=> "_o_"

但如果我们匹配x*,事情会变得奇怪:

"xox".gsub(/x*/, "_") #=> "__o__"

这是因为*匹配或更多次:

"" "x" "" "o" "" "x" ""
^^^^^^ ^^     ^^^^^^ ^^

如果我们减少零或更多"可能会更清楚。只有零:

"xox".gsub(/x{0}/, "_") #=> "_x_o_x_"

比赛是:

"" "x" "" "o" "" "x" ""
^^     ^^     ^^     ^^

在你的例子中也是如此。您匹配[^\/]零次或多次。正则表达式引擎匹配字符串末尾的bar[^\/] 3次)和之后的空白([^\/] 0次):

"/" "" "b" "" "a" "" "r" ""
    ^^^^^^^^^^^^^^^^^^^^ ^^