在Perl中使用Ctrl-C(SIGINT)打破系统命令的while循环?

考虑下面的例子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 } }