Ruby 1.9正则表达式匹配(un)引用的键值赋值

我希望匹配shell脚本,配置文件等中的键/值赋值,这可能是也可能不是单引号,双引号或反引号引用,哪些可能有也可能没有行尾注释。 例如,我想:

RAILS_ENV=production # => key: RAILS_ENV, value: production listen_address = 127.0.0.1 # localhost only by default # => key: listen_address, value: 127.0.0.1 PATH="/usr/local/bin" # => key: PATH, value: "/usr/local/bin" (or /usr/local/bin would be fine) HOSTNAME=`cat /etc/hostname` # => key: HOSTNAME, value: `cat /etc/hostname` 

如果你觉得很奇怪,它可以处理转义引号和引号内的#,但我不认为我会遇到任何问题。 如果你感觉不一样,你可以把它全部命名为-cat扩展风格和漂亮:

 CONFIG_LINE = %r{ (? export ){0} (? [\w-]+ ){0} (? \S* ){0} (? \#.*$ ){0} ^\s*(\g\s+)?\g\s*=\s*\g\s*(\g)?$ }x 

但我认为没有人真的像那样写regexen ..

我已经看到Regex引用带引号的字符串 ,但是我不能很好地将这些解决方案改编为可选引号; 我不太明白如何“期待一个结束报价,因此如果我有一个开始报价,就允许内部空间。”

编辑 :田人给出了一个实际的答案,所以现在我正在寻找纯粹的答案。 向我扔一些状态机,或告诉我为什么不能这样做。

它可能以一种正则表达式模式进行,但我相信保持模式简单。 正则表达式可能是阴险的,并隐藏许多小错误。 保持简单,避免这种情况,然后调整。

 text = < [["RAILS_ENV", "production"], ["listen_address ", " 127.0.0.1 # localhost only by default"], ["PATH", "\"/usr/local/bin\""]] 

要在后续map轻松删除尾随注释:

 text.scan(/^([^=]+)=(.+)/).map{ |n,v| [ n, v.sub(/#.+/, '') ] } # => [["RAILS_ENV", "production"], ["listen_address ", " 127.0.0.1 "], ["PATH", "\"/usr/local/bin\""]] 

如果要标准化所有名称/值,使它们没有多余的空格,您也可以在map执行此操作:

 text.scan(/^([^=]+)=(.+)/).map{ |n,v| [ n.strip, v.sub(/#.+/, '').strip ] } => [["RAILS_ENV", "production"], ["listen_address", "127.0.0.1"], ["PATH", "\"/usr/local/bin\""]] 

正则表达式“ /^([^=]+)=(.+)/ ”正在做的是:

  1. ^ ”是“在行的开头”,即“\ n”之后的字符。 这与字符串的开头不同,后者是\A 有一个重要的区别,所以如果你不理解这两个,最好先了解你何时以及为什么要使用一个而不是另一个。 这是正则表达式可以隐藏的那些地方之一。
  2. ([^=]+) ”是“捕获不等号的所有东西”。
  3. = ”显然是我们在上一步中寻找的等号。
  4. (.+) ”将在等号后捕捉所有内容。

我故意保持上述模式简单。 对于生产用途,我会使用一些“非贪婪”标志以及一个尾随的“ $ ”锚点来收紧模式:

 text.scan(/^([^=]+?)=(.+)$/).map{ |n,v| [ n.strip, v.sub(/#.+/, '').strip ] } => [["RAILS_ENV", "production"], ["listen_address", "127.0.0.1"], ["PATH", "\"/usr/local/bin\""]] 
  1. +? 意味着找到第一个匹配的’=’。 已经暗示使用[^=]但是+? 让我的意图更加明显。 没有这个我可以逃脱? 但它更像是一个自我记录的东西,供以后维护。 在你的用例中,它应该是良性的,但在你的Regex Bag’o Tricks中保留是值得的。
  2. $表示字符串结尾,即EOL,AKA行结束或回车之前的位置。 它也暗示了,但是将它插入模式中会使我更加明显的是我正在寻找的东西。

编辑跟踪OP的附加测试:

 text = < [["RAILS_ENV", "production"], ["listen_address", "127.0.0.1"], ["PATH", "\"/usr/local/bin\""], ["HOSTNAME", "`cat /etc/hostname`"]] 

如果我是为自己写这个,为了方便,我会生成一个哈希:

 Hash[ text.scan( /^ ( [^=]+? ) = ( .+ ) $/x ).map{ |n,v| [ n.strip, v.sub(/#.+/, '').strip ] } ] => {"RAILS_ENV"=>"production", "listen_address"=>"127.0.0.1", "PATH"=>"\"/usr/local/bin\"", "HOSTNAME"=>"`cat /etc/hostname`"} 

如果你想一次匹配所有这些,你就不会帮忙。 不同的配置文件具有不同的格式。

例如,你知道在一个shell文件中,变量不能以数字开头,之后只有字母/下划线,而且,如果引用它们,它们可以使用单引号或双引号,在这种情况下,转义一个或另一个是不同……而这就更不用说算术评估等了。

因此,仅针对shell变量,您必须使用多个正则表达式:

  • ^([A-Za-z_]\w*)=(.*)并捕获$ 1,这将为您提供变量名称;
  • 2美元,你有这些可能性

^"[^"]*(\\"[^"]*)*"$ #双引号中的值

^'[^']*('\\''[^']*)*'$ #单引号中的值

\$[A-Za-z_]\w*$ #simple variable interpolation`这甚至不考虑反引号值(可以嵌套!!)(如果它们不是,那么它很简单)。

这里有几个正则表达式,但它们甚至不会处理所有情况。