eval EXPR
eval BLOCK
eval

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 在列表上下文中返回语法错误,但不会返回运行时错误。)如果没有错误,$@ 被设置为空字符串。控制流运算符(如 lastgoto)可以绕过 $@ 的设置。请注意,使用 eval 既不会阻止 Perl 将警告打印到 STDERR,也不会将警告消息的文本塞入 $@ 中。要执行其中任何一项操作,您必须使用 $SIG{__WARN__} 功能,或者使用 no warnings 'all' 在 BLOCK 或 EXPR 中关闭警告。请参见 warnperlvarwarnings

请注意,由于 eval 会捕获其他致命错误,因此它对于确定特定功能(如 socketsymlink)是否已实现很有用。它也是 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 utf8no 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

如果要执行的代码没有变化,您可以使用 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 算作循环,因此循环控制语句 nextlastredo 不能用于离开或重新启动块。

BLOCK 中的最后一个分号(如果有)可以省略。