考虑下面的例子test.pl
:
#!/usr/bin/env perl use 5.10.1; use warnings; use strict; $SIG{'INT'} = sub {print "Caught Ctrl-C - Exit!\n"; exit 1;}; $| = 1; # turn off output line buffering use Getopt::Long; my $doSystemLoop = 0; GetOptions( "dosysloop"=>\$doSystemLoop ); print("$0: doSystemLoop is:$doSystemLoop (use " . (($doSystemLoop)?"system":"Perl") . " loop); starting...\n"); my $i=0; if (not($doSystemLoop)) { # do Perl loop while ($i < 1e6) { print("\tTest value is $i"); $i++; sleep 1; print(" ... "); sleep 1; print(" ... \n"); } } else { # do system call loop while ($i < 1e6) { system("echo","-ne","\tTest value is $i"); $i++; system("sleep 1"); system("echo","-ne"," ... "); system("sleep 1"); system("echo","-e"," ... "); } }
所以,如果我调用这个程序,所以它使用一个通常的Perl循环,一切都如预期的那样:
$ perl test.pl test.pl: doSystemLoop is:0 (use Perl loop); starting... Test value is 0 ... ... Test value is 1 ... ... Test value is 2 ... ^CCaught Ctrl-C - Exit! $
…就是,我按Ctrl-C,程序立即退出。
但是,如果while
循环的命令主要由system
调用组成,那么使用Ctrl-C几乎不可能退出:
$ perl test.pl --dosysloop test.pl: doSystemLoop is:1 (use system loop); starting... Test value is 0 ... ... Test value is 1 ... ... Test value is 2 ... ^C ... Test value is 3 ... ^C ... Test value is 4 ... ^C ... Test value is 5^C ... ^C ... Test value is 6^C ... ^C ... Test value is 7^C ... ^C ... Test value is 8^C ... ^C ... Test value is 9^C ... ^C ... Test value is 10 ... ^C ... Test value is 11^C ... ^C ... Test value is 12^C ... ... Test value is 13^Z [1]+ Stopped perl test.pl --dosysloop $ killall perl $ fg perl test.pl --dosysloop Terminated $
所以在上面的代码片段中,我正在像命令一样击中Ctrl-C( ^C
),并且程序完全忽略了我:/
然后我按Ctrl-Z( ^Z
)来作弊,这会停止进程并设置的背景; 然后在最后的shell中执行killall perl
,然后执行fg
命令,将Perl作业放回前台 – 由于killall
而终止。
我想要的是运行一个像这样的系统循环,可以打破它/用通常的Ctrl-C退出它。 这是可能的,我该怎么做?
Perl的信号处理机制推迟了信号的处理,直到安全点。 在perl虚拟机的操作码之间检查延迟信号。 由于system
和朋友算作一个操作码,所以只有exec'd命令终止时才检查信号。
这可以通过fork
来绕过,然后等待子进程的循环终止。 孩子也可以通过信号处理程序提前终止。
sub launch_and_wait { my $wait = 1; my $child; local $SIG{CHLD} = sub { $wait = 0; }; local $SIG{INT} = sub { $wait = 0; kill KILL => $child if defined $child; }; if ($child = fork()) { # parent while ($wait) { print "zzz\n"; sleep 1; } wait; # try to join the child } else { # child exec {$_[0]} @_; } } launch_and_wait sleep => 60; print "Done\n";
可能有很多方法可能出错(在孩子出生之前得到一个SIGINT)。 我也省略了任何错误处理。
检查system()
命令的退出状态是否有任何信号。 用SIGINT中断的外部命令将在这里得到“2”:
while () { system("sleep", 1); if ($? & 127) { my $sig = $? & 127; die "Caught signal INT" if $sig == 2; # you may also abort on other signals if you like } }