我多次使用红宝石的eval
功能。但是我听到有人说eval
是令人讨厌的。当被问到为什么以及如何,我永远无法得到令人信服的理由不使用它。他们真的很讨厌吗?如果是,以什么方式?评估有哪些“更安全”的选择?
答案 0 :(得分:33)
如果您eval
提交或由用户修改的字符串,这相当于允许任意代码执行。想象一下,如果字符串包含对rm -rf /
或类似的OS调用。也就是说,在您知道字符串被适当约束的情况下,或者您的Ruby解释器被适当地沙箱化,或者理想情况下两者兼而有之,eval
可以非常强大。
如果您熟悉,问题类似于SQL injection。这里的解决方案类似于注入问题的解决方案(参数化查询)。也就是说,如果您希望eval
的语句具有非常特定的形式,并且该语句的所有都需要由用户提交,则只有少数变量,数学表达式或类似表达式,您可以从用户那里获取这些小块,必要时对它们进行清理,然后使用插入适当位置的用户输入来评估安全模板语句。
答案 1 :(得分:11)
在Ruby中,有几个噱头可能比eval()
更合适:
#send
允许您调用名称为字符串并将参数传递给它的方法。yield
允许您将一段代码传递给将在接收方法的上下文中执行的方法。Kernel.const_get("String")
足以获得您的名称为字符串的类。我认为我无法详细解释它们,所以我只是给了你一些提示,如果你有兴趣,你会谷歌。
答案 2 :(得分:10)
eval
不仅不安全(正如其他地方所指出的那样),它也很慢。每次执行时,eval
ed代码的AST需要重新解析(对于例如JRuby,转向字节码),这是一个字符串繁重的操作,也可能对缓存局部性不利(在假设一个正在运行的程序没有eval
很多,并且解释器的相应部分因此缓存冷,除了很大之外。
为什么Ruby中存在eval
? “因为我们可以”主要 - 实际上,当eval
被发明时(对于LISP编程语言),它是mostly for show!更重要的是,当您想要“将解释器添加到解释器中”时,使用eval
是正确的事情,用于元编程任务,例如编写预处理器,调试器或模板引擎。这类应用程序的常见想法是按摩一些Ruby代码并在其上调用eval
,它肯定会重新发明并实现特定于域的玩具语言,这也是一个陷阱,也称为Greenspun's Tenth Rule。注意事项是:注意成本,例如对于模板引擎,在启动时执行所有eval
而不是运行时间;除非你知道如何“驯服”它,否则不要eval
不受信任的代码,即根据capability discipline的理论选择并强制执行该语言的安全子集。后者是非常困难的工作很多(参见例如how that was done for Java;我不知道Ruby的任何此类努力)。
答案 3 :(得分:7)
它使调试变得困难。它使优化变得困难。但最重要的是,这通常表明有更好的方法可以做任何你想做的事情。
如果您使用eval
告诉我们您要完成的工作,您可能会得到一些与您的具体情况相关的更相关的答案。
答案 4 :(得分:6)
Eval是一个非常强大的功能,应该谨慎使用。除了Matt J指出的安全问题之外,您还会发现调试运行时评估的代码非常困难。解释器难以表达运行时评估的代码块中的问题 - 因此查找它将很困难。
话虽如此,如果你对这个问题感到满意,并且不关心安全问题,那么你就不应该避免使用使ruby成为吸引人的功能之一。
答案 5 :(得分:5)
在某些情况下,位置良好的eval
很聪明,可以减少所需的代码量。除了Matt J提到的安全问题之外,您还需要问自己一个非常简单的问题:
当这一切都说完了之后,其他人都可以阅读你的代码并了解你的所作所为吗?
如果答案是否定的,那么您使用eval
获得的内容会因可维护性而被弃用。这个问题不仅适用于您在团队中工作,但它也适用于您 - 您希望能够回顾您的代码月,如果不是几年,也知道您做了什么。
答案 6 :(得分:-1)
如果你将从“外部”获得的任何东西传递给eval
,那么你做错了什么,这是非常讨厌的。 非常很难逃脱代码,因为它是安全的,所以我认为它非常不安全。但是,如果您使用eval来避免重复或其他类似的事情,例如下面的代码示例,则可以使用它。
class Foo
def self.define_getters(*symbols)
symbols.each do |symbol|
eval "def #{symbol}; @#{symbol}; end"
end
end
define_getters :foo, :bar, :baz
end
但是,至少在Ruby 1.9.1中,Ruby拥有非常强大的元编程方法,你可以改为:
class Foo
def self.define_getters(*symbols)
symbols.each do |symbol|
define_method(symbol) { instance_variable_get(symbol) }
end
end
define_getters :foo, :bar, :baz
end
对于大多数用途,您希望使用这些方法,并且不需要转义。
关于eval
的另一个坏处是事实(至少在Ruby中),它很慢,因为解释器需要解析字符串,然后在当前绑定中执行代码。其他方法直接调用C函数,因此你应该获得相当快的速度。