Ruby中奇怪的反斜杠替换

我不明白这个Ruby代码:

>> puts '\\ <- single backslash' # \ > puts '\\ <- 2x a, because 2 backslashes get replaced'.sub(/\\/, 'aa') # aa <- 2x a, because two backslashes get replaced 

到目前为止,一切如预期。 但是如果我们用/\\/搜索1,并用'\\\\'编码的2替换,为什么我们得到这个:

 >> puts '\\ <- only 1 ... replace 1 with 2'.sub(/\\/, '\\\\') # \ <- only 1 backslash, even though we replace 1 with 2 

然后,当我们用'\\\\\\'编码3时,我们只获得2:

 >> puts '\\ <- only 2 ... 1 with 3'.sub(/\\/, '\\\\\\') # \\ <- 2 backslashes, even though we replace 1 with 3 

任何人都能理解为什么在替换字符串中吞下反斜杠? 这发生在1.8和1.9。

这是一个问题,因为反斜杠(\)用作Regexps和字符串的转义字符。 您可以使用特殊变量\&来减少gsub替换字符串中的反斜杠数。

 foo.gsub(/\\/,'\&\&\&') #for some string foo replace each \ with \\\ 

编辑:我应该提到\&的值来自Regexp匹配,在这种情况下是一个反斜杠。

另外,我认为有一种特殊的方法来创建一个禁用转义字符的字符串,但显然不是。 这些都不会产生两个斜杠:

 puts "\\" puts '\\' puts %q{\\} puts %Q{\\} puts """\\""" puts '''\\''' puts < 

快速回答

如果您想避开所有这些混淆,请使用更少混淆的块语法 。 下面是一个用2个反斜杠替换每个反斜杠的示例:

 "some\\path".gsub('\\') { '\\\\' } 

可怕的细节

问题是当使用sub (和gsub )而没有块时,ruby会在替换参数中解释特殊字符序列 。 不幸的是, sub使用反斜杠作为这些的转义字符:

 \& (the entire regex) \+ (the last group) \` (pre-match string) \' (post-match string) \0 (same as \&) \1 (first captured group) \2 (second captured group) \\ (a backslash) 

像任何逃避一样,这会产生一个明显的问题。 如果要在输出字符串中包含上述序列之一(例如\1 )的文字值,则必须将其转义。 因此,要获得Hello \1 ,您需要替换字符串为Hello \\1 。 要在Ruby中将其表示为字符串文字,您必须再次将这些反斜杠转义为: "Hello \\\\1"

所以,有两种不同的逃避通行证 。 第一个采用字符串文字并创建内部字符串值。 第二个采用内部字符串值并用匹配数据替换上面的序列。

如果反斜杠后面没有与上述序列之一匹配的字符,则反斜杠(以及后面的字符)将不加改变地传递。 这也会影响字符串末尾的反斜杠 – 它将不加改变地传递。 在rubinius代码中最容易看到这个逻辑; 只需在String类中查找to_sub_replacement方法。

以下是String#sub如何解析替换字符串的一些示例

  • 1个反斜杠 \ (其字符串文字为"\\"

    传递不变,因为反斜杠位于字符串的末尾,并且后面没有字符。

    结果:

  • 2个反斜杠 \\ (其字符串文字为"\\\\"

    这对反斜杠匹配转义的反斜杠序列(参见上面的\\ )并转换为单个反斜杠。

    结果:

  • 3个反斜杠 \\\ (其字符串文字为"\\\\\\"

    前两个反斜杠与\\序列匹配,并转换为单个反斜杠。 然后最后的反斜杠位于字符串的末尾,因此它不会改变。

    结果: \\

  • 4个反斜杠 \\\\ (其字符串文字为"\\\\\\\\"

    两对反斜杠分别与\\序列匹配,并转换为单个反斜杠。

    结果: \\

  • 2个反斜杠,中间有一个字符\a\ (其字符串文字为"\\a\\"

    \a与任何转义序列都不匹配,因此允许它不加改变地通过。 尾随反斜杠也允许通过。

    结果: \a\

    注意:可以从\\a\\获得相同的结果(使用文字字符串: "\\\\a\\\\"

事后看来,如果String#sub使用了不同的转义字符,那么这可能不那么容易混淆了。 然后就不需要双重逃避所有的反斜杠了。

在我输入所有这些之后,我意识到\用于指代替换字符串中的组。 我想这意味着你需要在替换字符串中使用文字\\来替换\ 。 要得到一个文字\\你需要四个\ s,所以要用两个替换一个你实际需要八个(!)。

 # Double every occurrence of \. There's eight backslashes on the right there! >> puts '\\'.sub(/\\/, '\\\\\\\\') 

我错过了什么? 更有效的方法?

清除了作者第二行代码的一些混乱。

你说:

 >> puts '\\ <- 2x a, because 2 backslashes get replaced'.sub(/\\/, 'aa') # aa <- 2x a, because two backslashes get replaced 

这里没有替换2个反斜杠。 你用两个a('aa')替换1个转义反斜杠。 也就是说,如果你使用.sub(/\\/, 'a') ,你只会看到一个'a'

 '\\'.sub(/\\/, 'anything') #=> anything 

实际上,镐书提到了这个确切的问题。 这是另一种选择(来自最新版本的第130页)

 str = 'a\b\c' # => "a\b\c" str.gsub(/\\/) { '\\\\' } # => "a\\b\\c"