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"