eval
的所有形式都用于执行一小段 Perl 程序,捕获遇到的任何错误,以防止它们使调用程序崩溃。
没有参数的简单 eval
只是 eval EXPR
,其中表达式被理解为包含在 $_
中。因此,实际上只有两种 eval
形式;带表达式的形式通常称为“字符串 eval”。在字符串 eval 中,表达式的值(它本身是在标量上下文中确定的)首先被解析,如果没有任何错误,则在当前 Perl 程序的词法上下文中作为块执行。这种形式通常用于延迟解析和随后执行 EXPR 文本直到运行时。请注意,每次 eval
执行时都会解析该值。
另一种形式称为“块 eval”。它不如字符串 eval 通用,但 BLOCK 中的代码只解析一次(与解析 eval
本身周围的代码同时解析),并在当前 Perl 程序的上下文中执行。这种形式通常用于比第一种形式更有效地捕获异常,同时还提供在编译时检查 BLOCK 中代码的好处。BLOCK 只解析和编译一次。由于错误被捕获,它通常用于检查给定功能是否可用。
在这两种形式中,返回的值是 mini-program 中最后一个被评估的表达式的值;也可以使用 return 语句,就像子程序一样。提供返回值的表达式在 void、标量或列表上下文中被评估,具体取决于 eval
本身的上下文。有关如何确定评估上下文的更多信息,请参见 wantarray
。
如果存在语法错误或运行时错误,或者执行了 die
语句,eval
在标量上下文中返回 undef
,在列表上下文中返回空列表,并且 $@
被设置为错误消息。(在 5.16 之前,一个错误导致 undef
在列表上下文中返回语法错误,但不会返回运行时错误。)如果没有错误,$@
被设置为空字符串。控制流运算符(如 last
或 goto
)可以绕过 $@
的设置。请注意,使用 eval
既不会阻止 Perl 将警告打印到 STDERR,也不会将警告消息的文本塞入 $@
中。要执行其中任何一项操作,您必须使用 $SIG{__WARN__}
功能,或者使用 no warnings 'all'
在 BLOCK 或 EXPR 中关闭警告。请参见 warn
、perlvar 和 warnings。
请注意,由于 eval
会捕获其他致命错误,因此它对于确定特定功能(如 socket
或 symlink
)是否已实现很有用。它也是 Perl 的异常捕获机制,其中 die
运算符用于引发异常。
在 Perl 5.14 之前,对 $@
的赋值发生在恢复本地化变量之前,这意味着为了使您的代码在旧版本上运行,如果您想屏蔽一些错误,但不是所有错误,则需要一个临时变量。
# alter $@ on nefarious repugnancy only
{
my $e;
{
local $@; # protect existing $@
eval { test_repugnancy() };
# $@ =~ /nefarious/ and die $@; # Perl 5.14 and higher only
$@ =~ /nefarious/ and $e = $@;
}
die $e if defined $e
}
每种形式都有一些不同的注意事项。
由于 EXPR 的返回值在当前 Perl 程序的词法上下文中作为一个块执行,因此任何外部词法变量都对它可见,并且任何包变量设置或子程序和格式定义都将在之后保留。
请注意,当 BEGIN {}
块嵌入在 eval 块中时,块的内容将立即执行,并且在执行 eval 代码的其余部分之前执行。您可以通过以下方式完全禁用此功能:
local ${^MAX_NESTED_EVAL_BEGIN_BLOCKS} = 0;
eval $string;
这会导致$string
中的任何嵌入式BEGIN
块抛出异常。
"unicode_eval"
特性下如果启用了此特性(在use 5.16
或更高版本的声明下,默认情况下是启用的),Perl 假设 EXPR 是一个字符字符串。因此,字符串中的任何use utf8
或no utf8
声明都没有效果。源代码过滤器也被禁止。(但是,unicode_strings
可以出现在字符串中。)
另请参阅evalbytes
运算符,它可以与源代码过滤器正常工作。
"unicode_eval"
特性之外在这种情况下,行为是有问题的,并不容易描述。以下列出了两个无法轻易修复的错误,除非要破坏现有的程序
Perl 对 EXPR 的内部存储会影响执行代码的行为。例如
my $v = eval "use utf8; '$expr'";
如果 $expr 是 "\xc4\x80"
(UTF-8 中的 U+0100),那么存储在 $v
中的值将取决于 Perl 是以“升级”方式存储 $expr(参见 utf8)还是以其他方式存储
如果升级了,$v
将是 "\xc4\x80"
(即,use utf8
不会产生任何影响。)
如果未升级,$v
将是 "\x{100}"
。
这是不可取的,因为升级与否不应该影响字符串的行为。
在 eval
中激活的源代码过滤器会泄漏到当前正在编译的任何文件范围内。举一个使用 CPAN 模块 Semi::Semicolons 的例子
BEGIN { eval "use Semi::Semicolons; # not filtered" }
# filtered here!
evalbytes
修复了这个问题,使其按照预期的方式工作
use feature "evalbytes";
BEGIN { evalbytes "use Semi::Semicolons; # filtered" }
# not filtered
如果字符串扩展了一个包含浮点数的标量,则可能会出现问题。该标量可以扩展为字母,例如 "NaN"
或 "Infinity"
;或者,在 use locale
的范围内,小数点字符可能不是点(例如逗号)。这些都不太可能像你预期的那样解析。
你应该特别注意记住正在查看的内容
eval $x; # CASE 1
eval "$x"; # CASE 2
eval '$x'; # CASE 3
eval { $x }; # CASE 4
eval "\$$x++"; # CASE 5
$$x++; # CASE 6
上面案例 1 和 2 的行为完全相同:它们运行变量 $x 中包含的代码。(虽然案例 2 有误导性的双引号,让读者怀疑可能还会发生其他事情(实际上什么都没有发生)。)案例 3 和 4 同样以相同的方式运行:它们运行代码 '$x'
,该代码只返回 $x 的值。(案例 4 由于纯粹的视觉原因而更受欢迎,但它还有在编译时而不是在运行时编译的优点。)案例 5 是一个通常希望使用双引号的地方,但在这个特定情况下,您可以像案例 6 一样使用符号引用代替。
在 DB
包中定义的子例程中执行的 eval ''
不会看到通常的周围词法范围,而是看到调用它的第一个非 DB 代码段的范围。除非您正在编写 Perl 调试器,否则您通常不需要担心这个问题。
EXPR 的值中的最后一个分号(如果有)可以省略。
如果要执行的代码没有变化,您可以使用 eval-BLOCK 形式来捕获运行时错误,而无需每次都重新编译的代价。如果发生错误,它仍然会返回到 $@
中。示例
# make divide-by-zero nonfatal
eval { $answer = $a / $b; }; warn $@ if $@;
# same thing, but less efficient
eval '$answer = $a / $b'; warn $@ if $@;
# a compile-time error
eval { $answer = }; # WRONG
# a run-time error
eval '$answer ='; # sets $@
如果您想在加载 XS 模块时捕获错误,一些与二进制接口相关的问题(例如 Perl 版本偏差)即使使用 eval
也可能是致命的,除非设置了 $ENV{PERL_DL_NONLAZY}
。请参阅 perlrun。
在库中使用 eval {}
形式作为异常陷阱确实存在一些问题。由于当前 __DIE__
钩子的状态可能存在问题,您可能不希望触发用户代码可能已安装的任何 __DIE__
钩子。您可以为此目的使用 local $SIG{__DIE__}
结构,如以下示例所示
# a private exception trap for divide-by-zero
eval { local $SIG{'__DIE__'}; $answer = $a / $b; };
warn $@ if $@;
这一点尤其重要,因为 __DIE__
钩子可以再次调用 die
,这会改变它们的错误消息。
# __DIE__ hooks may modify error messages
{
local $SIG{'__DIE__'} =
sub { (my $x = $_[0]) =~ s/foo/bar/g; die $x };
eval { die "foo lives here" };
print $@ if $@; # prints "bar lives here"
}
由于这促进了远程操作,这种反直觉的行为可能会在将来的版本中得到修复。
eval BLOCK
不算作循环,因此循环控制语句 next
、last
或 redo
不能用于离开或重新启动块。
BLOCK 中的最后一个分号(如果有)可以省略。