如何在 Ruby 字符串中的正则表达式匹配之外替换?



给定如下示例输入:

s = "an example with 'one' word and 'two and three' words inside quotes"

我正在尝试迭代引号以外的部分以进行一些替换。例如,要将and转换为&但仅在引号之外才能获得:

an example with 'one' word & 'two and three' words inside quotes

如果我要更改引号内部,我可以简单地执行以下操作:

s.gsub(/'.*?'/){ |q| q.gsub(/and/, '&') }

获得:

an example with 'one' word and 'two & three' words inside quotes

我主要尝试了两件事来使这种策略适应报价之外

首先,我试图在第一个gsub中否定正则表达式(即/'.*?'/(。我想如果有像/v这样的后缀修饰符,我可以简单地做s.gsub(/'.*?'/v){ ... },不幸的是我找不到这样的东西。有一个负面的展望(即(?!pat)(,但我认为这不是我需要的。

其次,我尝试将splitgsub!一起使用:

puts s.split(/'.*?'/){ |r| r.gsub!(/and/, '&') }

使用split我可以遍历引号之外的部分:

s.split(/'.*?'/){ |r| puts r }

获得:

an example with 
word and 
words inside quotes

但是,我不能用gsubgsub!来改变块内的这些部分。我想我需要split的变异版本,类似于gsubscan的变异版本,但似乎没有这样的东西。

有没有一种简单的方法可以使这两种方法中的任何一种起作用?

您可以使用以下正则表达式执行所需的替换。

r = /G[^'n]*?(?:'[^'n]*'[^'n]*?)*?Kbandb/

启动引擎!

所需的 Ruby 代码如下。

str = "an and with 'one' word and 'two and three' words and end"
str.gsub(r, '&')
#=> "an & with 'one' word & 'two and three' words & end"

红宝石代码测试仪

Ruby 的正则表达式引擎执行以下操作。本质上,正则表达式断言"and"自上一个匹配项以来跟随偶数个单引号,或者如果它是第一个匹配项,则从字符串开头跟随偶数个单引号。

G          : asserts position at the end of the previous match
or the start of the string for the first match
[^'n]*?    : match 0+ chars other than ' and n, lazily
(?:         : begin capture group
'[^'n]*' : match ' then 0+ chars other than ' and n then '
[^'n]*?  : match 0+ chars other than ' and n, lazily
)           : end non-capture group
*?          : execute non-capture group 0+ times, lazily 
K          : forget everything matched so far and reset start of match
bandb/    : match 'and'

您可以匹配并捕获需要保留的内容,而只需匹配需要替换的内容。

s.gsub(/('[^']*')|and/) { $1 || '&' }
s.gsub(/('[^']*')|and/) { |m| m == $~[1] ? $~[1] : '&' }

如果需要将and匹配为一个完整的单词,请在模式中使用bandb而不是and

这种方法非常方便,因为您可以根据需要添加任意数量的要跳过的特定模式。 例如,您还希望避免在双引号之间匹配整个单词and

s.gsub(/('[^']*'|"[^"]*")|bandb/) { $1 || '&' }

或者,您要确保它也跳过带有转义引号的引号之间的字符串:

s.gsub(/('[^'\]*(?:\.[^'\]*)*'|"[^"\]*(?:\.[^"\]*)*")|bandb/m) { $1 || '&' }

或者,如果它出现在圆形、方形、尖括号和大括号之外:

s.gsub(/(<[^<>]*>|{[^{}]*}|([^()]*)|[[^][]*])|bandb/m) { $1 || '&' }

匹配和捕获单引号之间的子字符串,只匹配您需要更改的内容。如果组 1 匹配,请将其放回$1,否则,替换为&。第二行中的替换块仅检查最后一个匹配项的组 1 值是否与当前匹配的值相同,如果是,则将其放回原处,否则用&替换。

查看 Ruby 演示。

正则表达式详细信息

  • ('[^']*')- 捕获组 #1:'、除'以外的零个或多个字符,然后是一个'字符
  • |- 或
  • and-and子字符串。

最新更新