动态包含正则表达式的变量 string/generating 正则表达式和 \b 的问题

Variable containing regex as string/generating regex dynamically and trouble with \b

我有这些变量:

keywords = ["/(?=.*?\bTest1\b).*/i","/(?=.*?\bTest2\b)(?=.*?\bTest3\b).*(?m)^(?!.*?NotThis4)(?m)^(?!.*?NotThis5).*$/i"]

hash = {"Test2 Test3 irrelevant1"=>"Mon, 16 Feb 2015 09:26:02 +0000", "Test2 Test3 NotThis4 irrelevant2"=>"Mon, 16 Feb 2015 09:24:01 +0000", "Test1 irrelevant3 irrelevant4"=>"Mon, 16 Feb 2015 09:23:02 +0000"}

我需要 运行:

keywords.each do |regex|
  hash.select{ |k,_| k[regex]}
end

在此示例中,我尝试使用 "Test2 Test3 irrelevant1""Test1 irrelevant4 irrelevant5" 的键来收集哈希值。不过,正则表达式与我无关。它使用正则表达式 as/in 一个我无法理解的变量。我尝试将 \b 转义为 \b,但无济于事。

当我将变量设置为正则表达式时,例如:

regex = "/(?=.*?\bTest2\b)(?=.*?\bTest3\b).*(?m)^(?!.*?NotThis4)(?m)^(?!.*?NotThis5).*$/i"

代码:

hash.select{ |k,_| k[regex]}

不起作用。

但是如果我用实际的文字表达式替换变量:

hash.select{ |k, _| k[/(?=.*?\bTest2\b)(?=.*?\bTest3\b).*(?m)^(?!.*?NotThis4)(?m)^(?!.*?NotThis5).*$/i]}

它工作得很好。

此外,该功能也适用于文字字符串变量:

regex = "Test1"
hash.select{ |k, _| k[regex]}

以及文字字符串本身:

hash.select{ |k, _| k["Test1"]}

如何在变量中使用正则表达式,功能在顶部?再次强调一下:

keywords.each do |regex|
  hash.select{ |k,_| k[regex]}
end

正则表达式作为字符串接收:

keywords.map! do |array_lineitem|
        builder = ""
        last = ""
        array_lineitem.each do |string_element|
          if string_element[0] == "-"
                string_element.sub!(/^-/, '')
                last += "(?m)^(?!.*?" + string_element + ")"
            else 
                builder += "(?=.*?\b" + string_element + "\b)"  
            end
        end
        if last.empty?
            throwback = "/" + builder + ".*/i"  
        else 
            throwback = "/" + builder + ".*" + last + ".*$" + "/i"
        end
    end 

将字符串转换为正则表达式,我尝试了 to_regexp gem, the Regexp.escape, Regexp.union 和 eval(string),但还是没有成功。使用这些方法中的每一种,\b 都会转换为 \x08

为什么你认为它与 \b 有什么关系?

When I set a variable to a regular expression, such as:

   regex = "/(?=.*?\bTest2\b)(?=.*?\bTest3\b).*(?m)^(?!.*?NotThis4)(?m)^(?!.*?NotThis5).*$/i"

the code

hash.select{ |k,_| k[regex]}

您尚未将变量设置为正则表达式。您已将变量设置为以 / 开头和结尾的字符串,并且其中包含正则表达式的定义,true。要实际将变量设置为正则表达式,您不使用定义字符串的双引号,而是像这样:

>        regex = /(?=.*?\bTest2\b)(?=.*?\bTest3\b).*(?m)^(?!.*?NotThis4)(?m)^(?!.*?NotThis5).*$/i

现在您已将变量设置为正则表达式,而不是包含正则表达式源代码的字符串。

根据您的描述,我认为这可能是您的问题。如果您的问题实际上是正则表达式本身的定义与您想要的不匹配——这种情况经常发生在复杂的正则表达式中——最好的调试方法是从一个更简单的正则表达式开始,确认它与你想要的匹配,然后逐步构建复杂的正则表达式,确保每一步都符合您的预期。

可以动态生成一个带插值的正则表达式。正则表达式 // 文字支持使用 #{} 构造的字符串插值,与字符串文字相同。例如:

regex = /(?m)^(?!.*?#{string_element})/

如果你的 string_element 中有特殊的正则表达式控制字符,你可能想使用 Regex.escape,如果它是为了准确地表示其中的内容:

regex = /(?m)^(?!.*?#{Regexp.escape string_element})/

如果字符串中确实有正则表达式 定义,您可以从中创建一个正则表达式:

string = "some?(regex|or)something\Z"
regex  = Regexp.new(string)

puts string.class #=> String
puts regex.class #=> Regexp

我不确定你是否真的想在这里这样做,但你可以。我不得不承认我并不完全理解您想要做什么,并且我不相信您的方法是实现您实际总体目标的最佳方法。

但至于如何创建具有动态内插内容的正则表达式文字并将其保存在变量中,这不是问题,希望这会有所帮助。

这并不难,但看来您正在这样做:

foo = '\b[ab]'
Regexp.new(foo) # => /\b[ab]/
/#{foo}/ # => /\b[ab]/

或:

foo = "\b[ab]"
Regexp.new(foo) # => /\b[ab]/
/#{foo}/ # => /\b[ab]/

Ruby 非常乐意使用字符串来创建模式,您只需要把它做对即可。

字符串是模式的重要组成部分,因为我们可以从较小的部分构建模式,然后最终将我们想要的部分连接成一个大模式。我们也用各种语言来做到这一点,而不仅仅是 Ruby。

WORD_BOUNDARY = '\b'
WORD_CHARACTERS = '[a-zA-Z]'
WORD_PATTERN = /#{WORD_BOUNDARY}#{WORD_CHARACTERS}+#{WORD_BOUNDARY}/
WORD_PATTERN # => /\b[a-zA-Z]+\b/

/#{WORD_PATTERN}/ # => /(?-mix:\b[a-zA-Z]+\b)/
Regexp.new(WORD_PATTERN) # => /\b[a-zA-Z]+\b/

注意 "\b"'\b' 之间的区别也很重要。如果字符串允许插值变量和转义值,则 \b 将被视为退格键。这不是你想要的:

"\b" # => "\b"
"\b".ord # => 8

改为使用非解释字符串:

'\b' # => "\b"

或者双转义字界字符。

您可以轻松地动态生成模式,您只需要遵循字符串插值规则并了解如果字符串被插值,转义字符必须进行两次转义。

使用 Tin Man's 双转义字符串数组:

keywords = ["/(?=.*?\bTest1\b).*/i","/(?=.*?\bTest2\b)(?=.*?\bTest3\b).*(?m)^(?!.*?NotThis4)(?m)^(?!.*?NotThis5).*$/i"]

这个散列:

hash = {"Test2 Test3 irrelevant1"=>"Mon, 16 Feb 2015 09:26:02 +0000", "Test2 Test3 NotThis4 irrelevant2"=>"Mon, 16 Feb 2015 09:24:01 +0000", "Test1 irrelevant3 irrelevant4"=>"Mon, 16 Feb 2015 09:23:02 +0000"}

我可以使用 eval(foo) to convert a string version of a complete regex definition into jrochkind's(非字符串)正则表达式。安装'to_regexp'gemRegexp.try_convert(foo)Regexp.union(foo))也可以使用

keywords.map! do |string|
  eval(string) # or Regexp.try_convert(string) with the 'to_regexp' gem
end 

keywords.map do |regex|  
  hash.select{ |k, _| k[regex]}
end

获得想要的结果:

# => [{"Test1 irrelevant3 irrelevant4"=>"Mon, 16 Feb 2015 09:23:02 +0000"}, {"Test2 Test3 irrelevant1"=>"Mon, 16 Feb 2015 09:26:02 +0000"}]

我的实际代码现已更新,结构如下:

keywords = [["Test1"], ["Test2", "Test3", "-NotThis4", "-NotThis5"]]

hash = {"Test2 Test3 irrelevant1"=>"Mon, 16 Feb 2015 09:26:02 +0000", "Test2 Test3 NotThis4 irrelevant2"=>"Mon, 16 Feb 2015 09:24:01 +0000", "Test1 irrelevant3 irrelevant4"=>"Mon, 16 Feb 2015 09:23:02 +0000"}

keywords.map! do |array_lineitem|
        builder = ""
        last = ""
        array_lineitem.each do |string_element|
          if string_element[0] == "-"
                string_element.sub!(/^-/, '')
                last += '(?m)^(?!.*?' + string_element + ')'
            else 
                builder += '(?=.*?\b' + string_element + '\b)'  
            end
        end
        if last.empty?
            throwback = "/" + builder + ".*/i"  
        else 
            throwback = "/" + builder + ".*" + last + ".*$" + "/i"
        end
        eval(throwback) # or Regexp.try_convert(throwback) with the 'to_regexp' gem
    end

# => [/(?=.*?\bTest1\b).*/i, /(?=.*?\bTest2\b)(?=.*?\bTest3\b).*(?m)^(?!.*?NotThis4)(?m)^(?!.*?NotThis5).*$/i]

keywords.map do |regex|  
        hash.select{ |k, _| k[regex]}
    end

# => [{"Test1 irrelevant3 irrelevant4"=>"Mon, 16 Feb 2015 09:23:02 +0000"}, {"Test2 Test3 irrelevant1"=>"Mon, 16 Feb 2015 09:26:02 +0000"}]