提问人:Ilmari Karonen 提问时间:3/28/2023 更新时间:3/31/2023 访问量:84
Ruby 能保证 eval(str.inspect) == str 吗?
Does Ruby guarantee that eval(str.inspect) == str?
问:
我最近发现自己需要根据用户输入生成一个简单的 Ruby 脚本,其中一些脚本需要作为字符串文字包含在脚本中。虽然在我的特定情况下,此输入来自受信任的来源,但我仍然希望以一种即使输入字符串恰好包含引号、反斜杠、换行符、哈希标记或其他意外元字符也不会中断的方式执行此操作。
显而易见的解决方案(如前面问题的公认答案中所建议的那样)是使用 String#inspect
方法,其文档指出:
返回 str 的可打印版本,用引号括起来,特殊字符转义。
然而,该文档没有明确说明评估 Ruby 代码的输出将返回原始字符串。而且,事实上,我确实在技术上设法提出了一个使用非Unicode字符串的反例:String#inspect
pry(main)> str = 0x80.chr; eval(str.inspect) == str
=> false
但是,我需要编码的所有字符串都是 Unicode 字符串,因此这个反例对我来说只是理论上的兴趣。但我仍然想要一些记录在案的保证,因此存在以下问题:
- 如果是 Unicode 字符串,则保证等于 吗?
eval(str.inspect)
str
str
- 如果没有,是否还有其他方法可以在生成的 Ruby 代码中转义字符串文字,并保证始终有效?
另外,一个额外的问题:
- 总是保证相等吗?
eval("'" + str.gsub(/[\\']/, { "\\" => "\\\\", "'" => "\\'" }) + "'")
str
答:
让我试着总结一下我到目前为止的调查结果(包括马克斯现在删除的答案,它向我介绍了):String#dump
String#inspect
的文档不保证其输出会生成原始字符串。但是,至少从 Ruby 3.0.2 开始,String#dump
的文档确实做出了这样的保证:eval
此方法可用于往返:如果结果被求值,它将生成原始字符串。
new_str
因此,我的问题 #1 和 #2 的答案似乎是:
不,不能保证 Ruby 文档是等同的(尽管在实践中它似乎确实有效;见下文)。
eval(str.inspect)
str
OTOH,被记录为始终相等。
eval(str.dump)
str
当然,虽然拥有文档是件好事,但确保实际行为与文档内容相匹配也是一个好主意。
根据我的测试,从经验上讲,至少在相对现代的 Ruby 版本上,两者似乎都产生了输出,当 ed 时,等于原始 (Unicode) 字符串。String#inspect
String#dump
eval
具体来说,使用以下测试字符串(我相信它包含所有当前分配的非代理项 Unicode 字符,以及一些额外的潜在问题字符对和序列),
unicode_points = (0..0xD7FF).to_a + (0xE000..0xE007F).to_a
str = unicode_points.map { |i| i.chr(Encoding::UTF_8) }.join("")
str += "\#{foo} \\\\ \\\' \\\" \r\n\t"
似乎在 CRuby 2.6.10 和 3.3.0dev 以及 JRuby 9.3.10.0 上都评估为 true(这是我碰巧安装并方便使用的)。eval(str.inspect) == str
eval(str.dump) == str
然而,我的奖金问题 #3 中的方法并不完全有效;有问题的字符序列是(即 ASCII CR+LF),即使在单引号字符串中,它显然也会折叠成一个 LF。具体来说,事实证明(!gsub
"\r\n"
eval("'\r\n'") == "\n"
(我发现这一点是基于我在使用包含所有 Unicode 字符的字符串进行测试时收到的警告。这让我怀疑换行符可能存在一些有趣的解析,所以我添加到我的测试字符串中并得到了不匹配。warning: encountered \r in middle of line, treated as a mere space
"\r\n"
另外,在测试时,我碰巧注意到上面的测试字符串无法正确往返。一个演示相同问题的更简单的测试用例是 ,为此会引发 。String#dump
String#undump
str = "\u0001\uABCD"
str.dump.undump
RuntimeError: hex escape and Unicode escape are mixed
显然,问题在于将 ASCII C0 控制代码中的字符编码为形式的十六进制转义码,但形式中高于 U+007F 的非 ASCII Unicode 字符(或用于 BMP 之外的字符),由于某种原因不喜欢。虽然这不是 的问题,但 似乎很乐意接受 的输出,它可能仍然算作一个错误。我现在已将其报告为 https://bugs.ruby-lang.org/issues/19558。String#dump
\xNN
\uNNNN
\u{NNNNN}
String#undump
eval()
String#dump
评论