Break a while loop with system commands in Perl using Ctrl-C (SIGINT)?

Consider the following example: 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"," ... "); } } 

So, if I call this program, so it uses the usual Perl loop, everything is as expected:

 $ 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! $ 

... that is, I pressed Ctrl-C, the program will exit instantly.

However, if the commands in the while are mainly composed of system calls, it becomes almost impossible to exit with 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 $ 

So, in the above snippet, I hit Ctrl-C ( ^C ) like crazy, and the program completely ignores me. :/ Then I cheat by pressing Ctrl-Z ( ^Z ), which stops the process and sets it in the background; then in the resulting shell I do killall perl , and after that I execute the fg command, which will return the Perl task to the foreground - where it finally ends due to killall .

What I would like to have starts a system cycle like this, with the ability to break out / exit it with the usual Ctrl-C. Is it possible, and how to do it?

+4
source share
2 answers

The Perl signal processing engine defers signal processing to a safe point. Deferred signals are checked between Perl VM opcodes. Since system and friends are counted as a single opcode, signals are only checked after the exec'd command completes.

This can be bypassed by fork ing, and then wait in the loop to complete the child process. A child can also be completed earlier using a signal handler.

 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"; 

There are probably many ways that this could go wrong (getting SIGINT before the baby was born ...). I also skipped error handling.

+2
source

Check the output status of the system() command for any signals. An external command aborted by SIGINT will get "2" here:

 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 } } 
+2
source

Source: https://habr.com/ru/post/1498809/


All Articles