在什么情况下在Perl中跳过END块?

Gre*_*bet 6 perl

我有一个长期运行的程序,用于File::Temp::tempdir创建一个临时文件,有时通过它中断它^C.

以下程序打印它创建的临时目录的名称以及其中的文件名.

#!/usr/bin/env perl
use strict;
use warnings;
use File::Temp qw[tempdir];

my $dir = tempdir(CLEANUP => 1);
print "$dir\n";
print "$dir/temp.txt\n";

`touch $dir/temp.txt`;
exit;
Run Code Online (Sandbox Code Playgroud)

在OS X上,这会在里面创建一个目录 /var/folders

如果最后一行是exit;die;,则该文件夹将被清除,其中的临时文件将被删除.

但是,如果我们用最后一行替换sleep 20;然后通过中断perl程序^C,临时目录仍然存在.

% perl maketemp.pl
/var/folders/dr/cg4fl5m11vg3jfxny3ldfplc0000gn/T/ycilyLSFs6
/var/folders/dr/cg4fl5m11vg3jfxny3ldfplc0000gn/T/ycilyLSFs6/temp.txt
^C
% stat /var/folders/dr/cg4fl5m11vg3jfxny3ldfplc0000gn/T/ycilyLSFs6/temp.txt
16777220 6589054 -rw-r--r-- 1 <name> staff 0 0 "Aug  1 20:46:27 2016" "Aug  1 20:46:27 2016" "Aug  1 20:46:27 2016" "Aug  1 20:46:27 2016" 4096 0 0 
/var/folders/dr/cg4fl5m11vg3jfxny3ldfplc0000gn/T/ycilyLSFs6/temp.txt
%
Run Code Online (Sandbox Code Playgroud)

使用刚刚调用的信号处理程序来exit;清理目录.例如

#!/usr/bin/env perl
use strict;
use warnings;
use File::Temp qw[tempdir];

$SIG{INT} = sub { exit; };

my $dir = tempdir(CLEANUP => 1);
print "$dir\n";
print "$dir/temp.txt\n";

`touch $dir/temp.txt`;
sleep 20;
Run Code Online (Sandbox Code Playgroud)

正如使用"普通"信号处理程序一样

#!/usr/bin/env perl
use strict;
use warnings;
use File::Temp qw[tempdir];

$SIG{INT} = sub { };

my $dir = tempdir(CLEANUP => 1);
print "$dir\n";
print "$dir/temp.txt\n";

`touch $dir/temp.txt`;
sleep 20;
Run Code Online (Sandbox Code Playgroud)

我试着查看源代码(https://github.com/Perl-Toolchain-Gang/File-Temp/blob/master/lib/File/Temp.pm)以确定如何tempdir注册清理操作

这是退出处理程序安装

https://github.com/Perl-Toolchain-Gang/File-Temp/blob/master/lib/File/Temp.pm#L1716

哪个叫 _deferred_unlink

https://github.com/Perl-Toolchain-Gang/File-Temp/blob/master/lib/File/Temp.pm#L948

其中修改了全球哈希%dirs_to_unlink%files_to_unlink,而是采用PID $$作为重点出于某种原因(可能的情况下,Perl解释器叉?不知道为什么这是必要的,虽然因为删除目录看起来这将是一个幂等操作).

清理文件的实际逻辑就在这里,在END块中.

https://github.com/Perl-Toolchain-Gang/File-Temp/blob/master/lib/File/Temp.pm#L878

一个快速实验表明,END当perl正常或异常退出时,块确实会运行.

sleep 20;

END {
    print "5\n";
}

# does not print 5 when interrupted
Run Code Online (Sandbox Code Playgroud)

并在这里运行

$SIG{INT} = sub {};
sleep 20;

END {
    print "5\n";
}

# does print 5 when interrupted
Run Code Online (Sandbox Code Playgroud)

所以...为什么END在SIGINT之后跳过块,除非有一个信号处理程序,即使看起来它应该什么都不做?

ike*_*ami 6

默认情况下,SIGINT会终止进程[1].通过kill,我的意思是该进程立即被内核终止.该过程无法执行任何清理.

通过为SIGINT设置处理程序,您可以覆盖此行为.而不是杀死进程,而是调用信号处理程序.它可能没有做任何事情,但它的存在阻止了这个过程被杀死.在这种情况下,程序不会因信号而退出,除非它选择退出(通过调用dieexit处理程序.如果确实如此,它将有机会正常清理.

请注意,如果在系统调用期间输入了定义了处理程序的信号,则系统调用将退出并显示错误EINTR,以便程序可以安全地处理信号.这sleep就是收到SIGINT后立即返回的原因.

如果您使用过$SIG{INT} = 'IGNORE';,信号将被完全忽略.任何正在进行的系统调用都不会被中断.


  1. 在我的系统上,man 1 kill列出信号的默认操作.


mob*_*mob 5

您的信号处理程序$SIG{INT} = sub {}没有做任何事情,它是在捕获信号并阻止程序退出。

但是要回答您的原始问题,请按以下步骤END阻止perlmod

尽可能晚地执行,也就是说,在perl完成程序运行之后,就在解释器退出之前,即使它是由于die()函数而退出的。(但是,如果它是通过exec转换为另一个程序,或者被信号吹走了,则不是这样-您必须自己捕获该陷阱(如果可以)。)

也就是说,如果没有捕获到致命信号,它将绕过Perl的全局破坏,并且不会调用END块。