在Ruby中,转义和反转义字符串的最佳方法是什么?

39

Ruby是否有内置方法来转义和反转义字符串?过去,我使用正则表达式实现此功能;然而,我认为Ruby可能一直在内部执行此类转换。也许这个功能被某个地方公开了。

到目前为止,我已经想出了以下这些函数。它们能够工作,但看起来有点不正规:

def escape(s)
  s.inspect[1..-2]
end

def unescape(s)
  eval %Q{"#{s}"}
end

有更好的方法吗?


3
逃逸是为了什么目的?用于 Ruby 源代码中吗? - mu is too short
@mu太短了:是的,根据Ruby源代码规则进行转义。 - jwfearn
7个回答

26

Ruby 2.5新增了String#undump方法,作为String#dump方法的补充:

$ irb
irb(main):001:0> dumped_newline = "\n".dump
=> "\"\\n\""
irb(main):002:0> undumped_newline = dumped_newline.undump
=> "\n"

使用它:

def escape(s)
  s.dump[1..-2]
end

def unescape(s)
  "\"#{s}\"".undump
end

$irb
irb(main):001:0> escape("\n \" \\")
=> "\\n \\\" \\\\"
irb(main):002:0> unescape("\\n \\\" \\\\")
=> "\n \" \\"

19

有很多转义方法,其中一些如下:

# Regexp escapings
>> Regexp.escape('\*?{}.')   
=> \\\*\?\{\}\. 
>> URI.escape("test=100%")
=> "test=100%25"
>> CGI.escape("test=100%")
=> "test%3D100%25"

所以,这取决于您需要解决的问题。但我建议避免使用inspect进行转义。

更新-有一个转储,inspect使用它,看起来这就是您需要的:

>> "\n\t".dump
=> "\"\\n\\t\""

6
我也想避免使用 inspect。我希望能够使用 Ruby 自己的字符串转义代码。例如 Ruby.escape("\t") => "\\t"Ruby.unescape("\\t") => "\t" - jwfearn

17

Caleb函数是我能找到的最接近反向String#inspect的东西,不过它包含两个错误:

  • \\ 没有正确处理。
  • \x.. 保留了反斜杠。

我已经修复了上述错误,这是更新后的版本:

UNESCAPES = {
    'a' => "\x07", 'b' => "\x08", 't' => "\x09",
    'n' => "\x0a", 'v' => "\x0b", 'f' => "\x0c",
    'r' => "\x0d", 'e' => "\x1b", "\\\\" => "\x5c",
    "\"" => "\x22", "'" => "\x27"
}

def unescape(str)
  # Escape all the things
  str.gsub(/\\(?:([#{UNESCAPES.keys.join}])|u([\da-fA-F]{4}))|\\0?x([\da-fA-F]{2})/) {
    if $1
      if $1 == '\\' then '\\' else UNESCAPES[$1] end
    elsif $2 # escape \u0000 unicode
      ["#$2".hex].pack('U*')
    elsif $3 # escape \0xff or \xff
      [$3].pack('H2')
    end
  }
end

# To test it
while true
    line = STDIN.gets
    puts unescape(line)
end

3
谢谢更新!如果你评论了,我会修复它的。 - Caleb Fenton
@antirez 这非常有用。我已经将其纳入了我制作的木偶模块中作为 puppet function - gene_wood
@antirez 这是我迄今为止找到的最好的答案。只是一个提示,可以使用实际转义字符代替十六进制。例如,可以使用"\n"代替"\x0a"。我认为这更清晰明了。 - rigon

16

更新:我不再同意我的回答,但我宁愿不删除它,因为我怀疑其他人可能会走上这条错误的道路,并且对这个答案及其替代方案已经进行了很多讨论,所以我认为它仍然对对话做出了贡献,但请不要在实际代码中使用这个答案。

如果您不想使用eval,但愿意使用YAML模块,您可以使用它来代替:

require 'yaml'

def unescape(s)
  YAML.load(%Q(---\n"#{s}"\n))
end
YAML 相对于 eval 的优势在于其更安全,cane 禁止所有使用 eval 的方法。我曾经看到建议使用 $SAFEeval,但目前 JRuby 不支持该功能。
顺带一提,Python 原生支持反转义反斜杠

3
谢谢。我采纳了你的想法并将其应用到 JSON 上,JSON.parse("[#{s}]").first - akuhn
似乎 YAML 代码和 EVAL 代码是不同的。例如: s = "\xD8\x96a" YAML.load(%Q(---\n"#{s}"\n)) (eval %Q{"#{s}"}) 返回不同的值。 - MKo

13

可以使用 Ruby 的 inspect 方法:

    "a\nb".inspect
=> "\"a\\nb\""

通常情况下,如果我们打印一个包含换行符的字符串,会得到如下输出:

puts "a\nb"
a
b

如果我们打印检查的版本:

puts "a\nb".inspect
"a\nb"

将检查后的版本分配给一个变量,您将获得字符串的转义版本。

要撤消转义,请使用eval函数对字符串进行求值:

puts eval("a\nb".inspect)
a
b
我不是很喜欢这么做,这更像是我的好奇心,而不是我实践中会使用的方法。

6
警告,罗宾逊!如果字符串是用户输入的话,使用eval进行反转义非常危险!这将允许用户有效地运行几乎任何东西。 - James P McGrath
是的,如果它是用户输入,应该首先进行清理。但是,它不能运行任何东西,只能运行代码的用户ID可以运行的内容,在正确编写的应用程序中将减少特权或在chroot沙盒中运行。 - the Tin Man
你说得没错。但实际上,盒子上的价值很大程度上不在于操作系统文件,而在于你的数据。你可以尽可能地限制你的Rails应用程序,但它仍然需要访问你的数据库。因此,虽然攻击者不能做到“一切”,但他们仍然可以做很多事情,包括倾泄你的所有数据。 - James P McGrath

12

YAML的::unescape似乎不会转义引号字符,例如'"。我猜这是设计上的考虑,但这让我感到难过。

绝对不要在任意或由客户提供的数据上使用eval

这是我使用的方法。处理了我所看到的一切,并且不会引入任何依赖项。

UNESCAPES = {
    'a' => "\x07", 'b' => "\x08", 't' => "\x09",
    'n' => "\x0a", 'v' => "\x0b", 'f' => "\x0c",
    'r' => "\x0d", 'e' => "\x1b", "\\\\" => "\x5c",
    "\"" => "\x22", "'" => "\x27"
}

def unescape(str)
  # Escape all the things
  str.gsub(/\\(?:([#{UNESCAPES.keys.join}])|u([\da-fA-F]{4}))|\\0?x([\da-fA-F]{2})/) {
    if $1
      if $1 == '\\' then '\\' else UNESCAPES[$1] end
    elsif $2 # escape \u0000 unicode
      ["#$2".hex].pack('U*')
    elsif $3 # escape \0xff or \xff
      [$3].pack('H2')
    end
  }
end

为了处理扩展Unicode字符(如表情符号)的"\u{12345}"类型编码,我在正则表达式中添加了|u{([\da-fA-F]+)},例如/\\(?:([#{keys}])|u([\da-fA-F]{4})|u{([\da-fA-F]+)})|\\0?x([\da-fA-F]{2})/,将$3引用更改为$4,并在$2和$4部分之间插入了elsif $3; ["#$3".hex].pack('U*') - Grant Neufeld

5

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接