如何在Ruby中的其他正则表达式中嵌入正则表达式

时间:2017-03-27 22:42:56

标签: ruby regex

我有一个字符串:

'A Foo'

并希望在其中找到“Foo”。

我有一个正则表达式:

/foo/

我正在嵌入另一个不区分大小写的正则表达式中,所以我可以按步骤构建模式:

foo_regex = /foo/
pattern = /A #{ foo_regex }/i

但它不会正确匹配:

'A Foo' =~ pattern # => nil

如果我将文本直接嵌入到模式中,它可以工作:

'A Foo' =~ /A foo/i # => 0

怎么了?

2 个答案:

答案 0 :(得分:7)

从表面上看,似乎将模式嵌入到另一个模式中只会起作用,但这是基于模式在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:是这些选项在嵌入式模式中的表示方式。

根据Options的正则表达式文档:

  

imx也可以使用(?开关)构造在子表达式级别上应用,这将启用选项< em> 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[a b]) }/ # => /(?-mix:a|b)/

再次,source可以提供帮助:

/#{ Regexp.union(%w[a b]).source }/ # => /a|b/

了解所有这些:

foo_regex = /foo/
pattern = /#{ foo_regex.source }/i # => /foo/i
'A Foo' =~ pattern # => 2

答案 1 :(得分:1)

  

“怎么了?”

关于如何插入Regexp的假设是错误的。

通过在插值对象上调用#{...}来完成to_s的插值:

d = Date.new(2017, 9, 8)
#=> #<Date: 2017-09-08 ((2458005j,0s,0n),+0s,2299161j)>

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的实现方式,您可以在不丢失标志的情况下插入模式。这是一个功能,而不是一个错误。

如果Regexp#to_s只返回源代码(没有选项),它将按预期方式工作:

def foo_regex.to_s
  source
end

/A #{foo_regex}/i
#=> /A foo/i

以上代码仅用于演示目的,请勿执行此操作。