如何在Ruby中的其他正则表达式中嵌入正则表达式
我有一个字符串:
'A Foo'
并希望在其中找到“Foo”。
我有一个正则表达式:
/foo/
我正在嵌入另一个不区分大小写的正则表达式,所以我可以按步骤构建模式:
foo_regex = /foo/ pattern = /A #{ foo_regex }/i
但它不会正确匹配:
'A Foo' =~ pattern # => nil
如果我将文本直接嵌入到模式中,它可以工作:
'A Foo' =~ /A foo/i # => 0
怎么了?
从表面上看,将模式嵌入到另一个模式中似乎只会起作用,但这是基于模式在Ruby中如何工作的错误假设,它们只是字符串。 使用:
foo_regex = /foo/
创建一个Regexp对象:
/foo/.class # => Regexp
因此,它了解用于创建它的可选标志:
( /foo/ ).options # => 0 ( /foo/i ).options # => 1 ( /foo/x ).options # => 2 ( /foo/ix ).options # => 3 ( /foo/m ).options # => 4 ( /foo/im ).options # => 5 ( /foo/mx ).options # => 6 ( /foo/imx ).options # => 7
或者,如果你喜欢二进制:
'%04b' % ( /foo/ ).options # => "0000" '%04b' % ( /foo/i ).options # => "0001" '%04b' % ( /foo/x ).options # => "0010" '%04b' % ( /foo/xi ).options # => "0011" '%04b' % ( /foo/m ).options # => "0100" '%04b' % ( /foo/mi ).options # => "0101" '%04b' % ( /foo/mx ).options # => "0110" '%04b' % ( /foo/mxi ).options # => "0111"
并且无论何时使用Regexp,无论是作为独立模式还是嵌入到另一个模式中,都会记住它们。
如果我们在嵌入后查看模式的样子,您可以看到这一点:
/#{ /foo/ }/ # => /(?-mix:foo)/ /#{ /foo/i }/ # => /(?i-mx:foo)/
?-mix:
和?i-mx:
这些选项是如何在嵌入模式中表示的。
根据选项的Regexp文档:
i
,m
和x
也可以使用(?on-off)构造应用于子表达式级别,该构造启用选项,并禁用括号括起来的表达式的选项。
因此,Regexp正在记住这些选项,即使在外部模式中,也会导致整体模式失败:
pattern = /A #{ foo_regex }/i # => /A (?-mix:foo)/i 'A Foo' =~ pattern # => nil
可以确保所有子表达式与其周围的模式匹配,但是这很快就会变得过于复杂或混乱:
foo_regex = /foo/i pattern = /A #{ foo_regex }/i # => /A (?i-mx:foo)/i 'A Foo' =~ pattern # => 0
相反,我们有source
方法返回模式的文本:
/#{ /foo/.source }/ # => /foo/ /#{ /foo/i.source }/ # => /foo/
使用其他Regexp方法(例如union
时,嵌入式模式记住选项的问题也会出现:
/#{ Regexp.union(%w[ab]) }/ # => /(?-mix:a|b)/
再次, source
可以帮助:
/#{ Regexp.union(%w[ab]).source }/ # => /a|b/
知道这一切:
foo_regex = /foo/ pattern = /#{ foo_regex.source }/i # => /foo/i 'A Foo' =~ pattern # => 2
“怎么了?”
关于如何插入Regexp
假设是错误的。
通过在插值对象上调用to_s
来完成#{...}
的插值:
d = Date.new(2017, 9, 8) #=> # d.to_s #=> "2017-09-08" "today is #{d}!" #=> "today is 2017-09-08!"
而不仅仅是字符串文字,还有正则表达式文字:
/today is #{d}!/ #=> /today is 2017-09-08!/
在您的示例中,要插入的对象是Regexp
:
foo_regex = /foo/
并且Regexp#to_s
返回:
[…]正则表达式及其选项使用(?opts:source)表示法。
foo_regex.to_s #=> "(?-mix:foo)"
因此:
/A #{foo_regex}/i #=> /A (?-mix:foo)/i
就像:
"A #{foo_regex}" #=> "A (?-mix:foo)"
换句话说:由于Regexp#to_s
的实现方式,您可以在不丢失标志的情况下插入模式。 这是一个function,而不是一个bug。
如果Regexp#to_s
只返回源(没有选项),它将按照您期望的方式工作:
def foo_regex.to_s source end /A #{foo_regex}/i #=> /A foo/i
以上代码仅用于演示目的,请勿执行此操作。