我最近发现自己需要根据用户输入生成一个简单的Ruby脚本,其中一些需要作为字符串文字包含在脚本中。虽然在我的特定情况下,输入来自可信的来源,但我仍然希望以一种即使输入字符串碰巧包含引号,反斜杠,换行符,哈希标记或其他意外的元字符也不会中断的方式来完成这一工作。
显而易见的解决方案(如this earlier question的公认答案中所推荐的)是使用String#inspect
方法,其文档称:
返回 str 的可打印版本,用引号括起来,特殊字符被转义。
然而,文档中并没有明确说明将String#inspect
的输出作为Ruby代码进行求值将返回原始字符串。事实上,我确实在技术上设法使用非Unicode字符串提出了一个反例:
pry(main)> str = 0x80.chr; eval(str.inspect) == str
=> false
然而,我需要编码的所有字符串都是Unicode字符串,所以这个反例对我来说只是理论上的兴趣。但我仍然希望得到一些文档化的保证,因此有以下问题:
1.如果str
是Unicode字符串,那么eval(str.inspect)
是否保证等于str
?
1.如果没有,是否有其他方法可以在生成的Ruby代码中转义字符串文字,并保证始终有效?
还有一个问题:
eval("'" + str.gsub(/[\\']/, { "\\" => "\\\\", "'" => "\\'" }) + "'")
是否总是保证等于str
?
1条答案
按热度按时间0sgqnhkj1#
让我试着总结一下目前为止的调查结果(包括Max的现已删除的answer,它将我介绍给了
String#dump
):String#inspect
的文档并不保证eval
的输出会产生原始字符串。但是,至少从Ruby 3.0.2开始,String#dump
的文档 * 确实 * 保证了这一点:此方法可用于往返:如果结果
new_str
被eval'艾德,它将产生原始字符串。因此,我的问题#1和#2的答案似乎是:
1.不,Ruby文档并不保证
eval(str.inspect)
等于str
(尽管在实践中它似乎确实有效;见下文)。eval(str.dump)
* 被记录为始终等于str
。当然,虽然有文档很好,但确保实际行为与文档中的行为相匹配也是一个好主意。
根据我的测试,至少在相对现代的Ruby版本上,* 凭经验 *,
String#inspect
和String#dump
seem 产生的输出在eval
编辑时等于原始(Unicode)字符串。具体来说,使用下面的测试字符串(我相信它包含了所有当前分配的非代理Unicode字符,以及一些额外的潜在问题字符对和序列)
看起来
eval(str.inspect) == str
和eval(str.dump) == str
在CRuby 2.6.10和3.3.0dev以及JRuby www.example.com上的计算结果都为true9.3.10.0(我碰巧安装了这些工具,并且可以方便地使用它们)。然而,我的附加问题#3中的
gsub
方法并不完全有效;有问题的字符序列是"\r\n"
(即ASCII CR+LF),即使在单引号字符串中,它也明显地被折叠成单个LF。具体地说,结果是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
。显然,问题在于
String#dump
将ASCII C0 control codes中的字符编码为\x*NN*
形式的十六进制转义码,但将U+007 F以上的非ASCII Unicode字符编码为\u*NNNN*
形式(或\u{*NNNNN*}
用于BMP以外的字符),由于某种原因String#undump
不喜欢。虽然这不是eval()
的问题,它似乎很高兴地接受了String#dump
的输出,它可能仍然算作一个bug。我现在已经将其报告为https://bugs.ruby-lang.org/issues/19558。