【问题标题】:Weird backslash substitution in RubyRuby中奇怪的反斜杠替换
【发布时间】:2010-12-05 06:14:59
【问题描述】:

我不明白这段 Ruby 代码:

>> puts '\\ <- single backslash'
# \ <- 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 上。

【问题讨论】:

    标签: ruby regex backslash


    【解决方案1】:

    快速解答

    如果您想避开所有这些混淆,使用更容易混淆的块语法。这是一个将每个反斜杠替换为 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 class 中查找to_sub_replacement 方法即可。

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

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

      因为反斜杠在字符串的末尾并且后面没有字符,所以原封不动地通过。

      结果: \

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

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

      结果: \

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

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

      结果: \\

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

      两对反斜杠各匹配\\ 序列并转换为单个反斜杠。

      结果: \\

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

      \a 不匹配任何转义序列,因此允许它原封不动地通过。尾部的反斜杠也允许通过。

      结果: \a\

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

    事后看来,如果String#sub 使用了不同的转义字符,这可能不会那么令人困惑。那么就不需要双重转义所有反斜杠。

    【讨论】:

    • 对于捕获的组,我最多只能匹配 9 个组,然后我得到第 1 组的匹配,然后是 0 和 1 等...例如考虑第一组匹配是“
    • 很遗憾,Ruby 文档中根本没有包含这些信息(例如ruby-doc.org/core-2.1.4/String.html#method-i-gsub)。它应该。
    • @Peter 这应该是选择的答案。此外,令人震惊。
    【解决方案2】:

    澄清作者第二行代码的一点困惑。

    你说:

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

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

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

    【讨论】:

    • 对不起,完全正确。这更像是一个错字而不是误解。
    【解决方案3】:

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

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

    【讨论】:

    • 不错,神级文档
    【解决方案4】:

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

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

    编辑:我应该提到 \& 的值来自正则表达式匹配,在这种情况下是单个反斜杠。

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

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

    【讨论】:

    • 嗯,有趣的方法。少一点“纯粹”,因为如果您进行更复杂的搜索,它将无法正常工作。但是字符肯定更少...
    【解决方案5】:

    啊,在我输入所有这些之后,我意识到\ 用于引用替换字符串中的组。我想这意味着您需要在替换字符串中使用文字 \\ 来替换 \。要获得文字 \\,您需要四个 \s,因此要将一个替换为两个,您实际上需要八个(!)。

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

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

    【讨论】:

    • 我认为你是对的。但韦尔奇的方式对我来说似乎更好。
    猜你喜欢
    相关资源
    最近更新 更多
    热门标签