gsub部分替换

时间:2020-03-06 14:34:40  来源:igfitidea点击:

我只想在此表达式中替换括号中的组:

my_string.gsub(/<--MARKER_START-->(.)*<--MARKER_END-->/, 'replace_text')

这样我得到:&lt;-MARKER_START-> replace_text &lt;-MARKER_END->

我知道我可以在替换表达式中重复整个MARKER_STARTMARKER_END块,但是我认为应该有一个更简单的方法来执行此操作。

解决方案

我们可以执行以下操作:

my_string.gsub(/(<--MARKER_START-->)(.*)(<--MARKER_END-->)/, 'replace_text')

我们可以使用零宽度的超前声明和后向声明。

这个正则表达式应该在ruby 1.9和perl以及许多其他地方工作:

注意:ruby 1.8仅支持预定义断言。我们需要先行和后行才能正确执行此操作。

s.gsub( /(?<=<--MARKER START-->).*?(?=<--MARKER END-->)/, 'replacement text' )

在ruby 1.8中发生的事情是?&lt;=导致它崩溃,因为它不理解后向断言。对于那部分,我们必须回退到使用诸如Greig Hewgill提到的反向引用

所以你得到的是

s.gsub( /(<--MARKER START-->).*?(?=<--MARKER END-->)/, 'replacement text' )

解释第一:

我将正则表达式中间的(。)*替换为。*?`,这是非贪婪的。
如果我们没有贪婪,那么如果我们在一行上有2个标记,则正则表达式将尽力匹配。最好通过示例说明:

"<b>One</b> Two <b>Three</b>".gsub( /<b>.*<\/b>/, 'BOLD' )
=> "BOLD"

我们真正想要的是:

"<b>One</b> Two <b>Three</b>".gsub( /<b>.*?<\/b>/, 'BOLD' )
=> "BOLD Two BOLD"

解释之二:

零宽度超前断言听起来像一大堆书呆子混乱。

"先行断言"实际上的意思是"如果我们要寻找的东西,则只有匹配项之后才是其他内容。

例如,仅匹配一个数字,如果后跟一个F。

"123F" =~ /\d(?=F)/ # will match the 3, but not the 1 or the 2

"零宽度"的实际含义是"在搜索中考虑"跟随",但在进行替换或者分组或者类似操作时,请勿将其视为匹配项的一部分。
使用相同的123F示例,如果我们不使用先行断言,而是执行以下操作:

"123F" =~ /\dF/ # will match 3F, because F is considered part of the match

如我们所见,这是检查我们的<-MARKER END->的理想选择,但是我们对于&lt;-MARKER START->的需求是能够说"只有匹配,如果我们正在寻找的东西跟在这其他东西之后"。这称为后置断言,ruby 1.8出于某种奇怪的原因而没有。

希望有道理:-)

PS:为什么要使用前瞻性断言而不是仅使用反向引用?如果使用前瞻,实际上并没有取代&lt;-MARKER->位,而是仅取代了内容。如果使用反向引用,则将替换全部。我不知道这是否会对性能造成很大的影响,但是从编程的角度来看,这似乎是正确的选择,因为我们实际上根本不想替换标记。