我目前正在研究Ruby中的正则表达式,有一件事我不明白:
bash
我知道一个空的正则表达式应该匹配任何字符串,但为什么2.3.3 :015 > 'abc'.scan(//)
=> ["", "", "", ""]
2.3.3 :016 > 'abc'.match(//)
=> #<MatchData "">
在这个例子中返回四个匹配?它是否计算字符串的开头和结尾,然后计算字符之间的每个“空格”?这个结果背后的机制是什么?
答案 0 :(得分:3)
正则表达式//
与任何字符串都不匹配;它匹配任何零长度字符串。
但是让我们回来吧。考虑一下:
"ab".scan(/[a-z]/) # => ["a", "b"]
这很容易理解。 String#scan
从索引0开始,立即找到匹配项a
,然后将其添加到结果数组,现在为["a"]
。然后它将其内部“光标”前进到匹配文本之后的第一个索引,该文本恰好为1,再次找到匹配项。现在它的结果是["a", "b"]
。最后,它前进到索引2,它超过字符串的末尾,因此它会停止。
现在考虑你的例子:
"abc".scan(//)
这几乎是一样的。 String#scan
从索引0开始,立即找到匹配项:a
之前的零长度字符串。它将它添加到结果数组中,现在是[""]
。但现在我们遇到了麻烦。因为匹配的长度是0,当匹配后String#scan
前进到第一个索引时,它仍然会在索引0处。它应该进入无限循环,对吧?
此时,正则表达式引擎做出了一个执行决策。因为前一个匹配的长度为零,而不是落入无限循环,所以它将光标前进一个字符。现在索引是1,然后它找到另一个匹配:b
之前的零长度字符串。现在它的结果是["", ""]
。它再次将光标前进一个字符并在c
之前找到零长度字符串,并再次找到字符串结尾之前(c
之后)的零长度字符串。那么它的最终结果是["", "", "", ""]
。
这是调试String#scan
的一个方便的习惯用语:
"abc".scan(//) do
p $~, $~.offset(0)
end
$~
是Regexp.last_match
的别名,它返回上一场比赛的MatchData对象。 $~.offset(0)
返回一个数组,其中包含匹配开头和结尾的索引($~.offset(1)
将返回第一个捕获组的开头和结尾,依此类推)。在这种情况下,我们得到:
#<MatchData "">
[0, 0]
#<MatchData "">
[1, 1]
#<MatchData "">
[2, 2]
#<MatchData "">
[3, 3]
如您所见,每次匹配空字符串(""
)时,第一个匹配位于索引0,最后一个匹配位于索引3(字符串中最后一个字符后面) 。在repl.it上尝试:https://repl.it/GL3N
您可以在({当然)Regular-Expressions.info阅读有关不同正则表达式引擎如何处理零长度匹配的更多信息。