如何编写不在 eval 块中触发的 SIG{__DIE__} 处理程序?

How can I write a SIG{__DIE__} handler that does not trigger in eval blocks?

根据 perldoc -f die,哪些文档 $SIG{__DIE__}

Although this feature was to be run only right before your program was to exit, this is not currently so: the $SIG{__DIE__} hook is currently called even inside evaled blocks/strings! If one wants the hook to do nothing in such situations, put die @_ if $^S; as the first line of the handler (see $^S in perlvar). Because this promotes strange action at a distance, this counterintuitive behavior may be fixed in a future release.

所以让我们采用一个基本的信号处理程序,它将用 eval { die 42 }

触发
package Stupid::Insanity {
  BEGIN { $SIG{__DIE__} = sub { print STDERR "ERROR"; exit; }; }
}

我们通过

确保安全
package Stupid::Insanity {
  BEGIN { $SIG{__DIE__} = sub { return if $^S; print STDERR "ERROR"; exit; }; }
}

现在 不会 触发 eval { die 42 },但当相同代码位于 BEGIN {} 块中时它会触发

BEGIN { eval { die 42 } }

这可能看起来晦涩难懂,但它是真实世界,正如您所看到的 , or in my case specifically here Net::DNS::Parameters。你可能认为你也可以抓住编译阶段,像这样,

BEGIN {
  $SIG{__DIE__} = sub {
    return if ${^GLOBAL_PHASE} eq 'START' || $^S;
    print STDERR "ERROR";
    exit;
  };
}

这将适用于上述情况,但遗憾的是它将不适用于对其中​​包含 BEGIN 语句的文档的要求,

eval "BEGIN { eval { die 42 } }";

有没有办法解决这个问题并编写一个不干扰 eval$SIG{__DIE__} 处理程序?

勾选caller(1)

[caller(1)]->[3] eq '(eval)'

的兔子洞再往下一点
return if [caller(1)]->[3] eq '(eval)' || ${^GLOBAL_PHASE} eq 'START' || $^S;

抓取整个调用堆栈

您可以抓取整个堆栈并确保您没有深入评估,

for ( my $i = 0; my @frame = caller($i); $i++ ) {
  return if $frame[3] eq '(eval)'
}

是的,这完全是精神错乱。感谢 mst on irc.freenode.net/#perl 的指点。