简单的起点是:
words = %w[one two three]
/#{ Regexp.union(words).source }/i # => /one|two|three/i
您可能想确保您只匹配单词,因此将其调整为:
/\b#{ Regexp.union(words).source }\b/i # => /\bone|two|three\b/i
为了整洁和清晰,我更喜欢使用非捕获组:
/\b(?:#{ Regexp.union(words).source })\b/i # => /\b(?:one|two|three)\b/i
使用source 很重要。当您创建 Regexp 对象时,它会知道适用于该对象的标志(i、m、x)并将这些标志插入到字符串中:
"#{ /foo/i }" # => "(?i-mx:foo)"
"#{ /foo/ix }" # => "(?ix-m:foo)"
"#{ /foo/ixm }" # => "(?mix:foo)"
或
(/foo/i).to_s # => "(?i-mx:foo)"
(/foo/ix).to_s # => "(?ix-m:foo)"
(/foo/ixm).to_s # => "(?mix:foo)"
当生成的模式独立时很好,但是当它被插入到字符串中以定义模式的其他部分时,标志会影响每个子表达式:
/\b(?:#{ Regexp.union(words) })\b/i # => /\b(?:(?-mix:one|two|three))\b/i
深入研究 Regexp 文档,您会看到 ?-mix 关闭了 (?-mix:one|two|three) 内部的“忽略大小写”,即使整体模式被标记为 i,导致模式不起作用你想要什么,而且真的很难调试:
'foo ONE bar'[/\b(?:#{ Regexp.union(words) })\b/i] # => nil
相反,source 删除了内部表达式的标志,使模式符合您的预期:
/\b(?:#{ Regexp.union(words).source })\b/i # => /\b(?:one|two|three)\b/i
和
'foo ONE bar'[/\b(?:#{ Regexp.union(words).source })\b/i] # => "ONE"
您可以使用Regexp.new 并传入标志来构建您的模式:
regexp = Regexp.new('(?:one|two|three)', Regexp::EXTENDED | Regexp::IGNORECASE) # => /(?:one|two|three)/ix
但随着表达式变得越来越复杂,它变得笨拙。使用字符串插值构建模式仍然更容易理解。