Beefy Boxes and Bandwidth Generously Provided by pair Networks
Perl Monk, Perl Meditation
 
PerlMonks  

SIGINT in system() with shell metachars

by cadphile (Beadle)
on Jun 24, 2003 at 22:42 UTC ( [id://268704]=perlquestion: print w/replies, xml ) Need Help??

cadphile has asked for the wisdom of the Perl Monks concerning the following question:

The Perl exec and system commands have two argument formats:

1. single string argument (invokes a shell and interprets the string as a shell command).

system( "ls *.txt" );

2. a list of more than one element (invokes the list elements as a program, without shell interpolation).

my @list=("someprog", "progarg1", "progarg2"); system( @list );

In the first format, SIGINT cannot be captured in the return status of the system() call, but in the second format, if a signal is sent to the program, it is passed into the Perl script through the system() return status.

I need to capture SIGINT, but I need to use system commands with piping, and also redirecting the output to a tmpfile.

I've searched for some CPAN module that might already do this but haven't found anything. Something like this:

$sysret = system("prog1 file1 | filter -v $version > $tmpfile 2>&1"); $syssig = $sysret & 0x7f;

Splitting the pipe, and redirection operators into separate list elements, and then using the list format, doesn't work: the operators are not interpreted properly.

Any suggestions??

Thanks!
-cadphile

Replies are listed 'Best First'.
Re: SIGINT in system() with shell metachars
by cadphile (Beadle) on Jun 25, 2003 at 00:43 UTC
    An addendum: (BTW, I'm running in Solaris8.) After posting this, I ran yet more tests, and discovered that when I run system() with just one argument, thus invoking shell interpolation, the return code from system() is reorganized.

    Programming Perl (page 812) describes the system() return value as follows (for system with a LIST argument):

    $exit_value = $? >> 8; $signal_num = $? & 127; # or 0x7f ... $dumped_core = $? & 128; # or 0x80 ...

    I.e. the return status is in the HIGH byte, and the signal received, and coredump status, are encoded in the LO byte.

    But in the single argument form of system(), I notice that the LO byte has nothing in it. The HIGH byte seems to be encoded as follows:

    $high_byte = $? >> 8; $was_interrupted = $high_byte & 0x80; $status = $high_byte & 0x7f;
    If $was_interrupted is 0, then $status is the return status of the shell command. If $was_interrupted is 1, then $status is the signal number that interrupted the child shell.

    I'm posting this because I haven't seen this documented anywhere. Does anyone know this stuff already? Where (if at all) is this documented? Is this a Solaris feature, or a Perl feature??

    thanks!
    -cadphile

      You should realize that with the single argument form of system(), the shell may be called and passed the argument string (whether or not it's called depends on the content of the string, and also on the OS). If the shell is called, you will get back the exit status of the shell - not the exit status of the program(s) mentioned in the argument string (after all, Perl calls the shell if Perl finds the argument string to complex to deal with it, so Perl doesn't know what is eventually called). What the shell will return might be platform dependend.

      Abigail

      This is probably a POSIX-only feature, but I might be wrong - you probably won't see signals at all on other systems. If you do happen to be in a Unixy environment -- Linux, Solaris, *BSD, et al. -- the following should probably do what you want:

      #!/usr/bin/perl use POSIX qw{:signal_h}; if (system('for a in 1 2 3 4; do echo $a>>/tmp/foobar; echo tick $a; s +leep 5; done') != 0) { my $sig_if_any = $? & 127; my $return_value = $? >> 8; print "child died: rv=$return_value, signal=$sig_if_any\n"; if (defined($sig_if_any) && ($sig_if_any == SIGINT)) { print "child was interrupted\n"; # ... do something appropriate ... } # ... cleanup ... } else { # ... do something with /tmp/foobar } ## ... run, and hit ctrl-c while it ticks...

      The usual caveats about interpolating Perl variables into the strings passed to system() apply, of course :). Any monks more cross-platformy than me care to comment on the applicability of $? on other systems?

Re: SIGINT in system() with shell metachars
by chip (Curate) on Jun 24, 2003 at 23:48 UTC
    You could just temporarily ignore SIGINT just like C's system() does:

    { local $SIG{INT} = 'IGNORE'; $ret = system @whatever }

        -- Chip Salzenberg, Free-Floating Agent of Chaos

      Perl's system() already ignores SIGINT and SIGQUIT, as well as Perl's backticks. The question is how to get the correct return value back from system().
        Ah, quite so. You are up against a fug/beature in the shell, i.e. that when the shells spawns kids killed by a signal, the shell doesn't exit with a signal-like exit status.

        I suggest using the 'trap' shell statement to generate a non-standard exit status from signal delivery. There are some timing quirks that may hit you if the child is doing things like saving and restoring tty modes, but otherwise it works (I just tested it on Linux):

        system(q[ trap 'exit 77' 2; foo | bar ]); if (($? >> 8) == 77) { wow_i_got_a_sigint(); }

        The shell's 'trap' statement is a handy feature, seldom used.

            -- Chip Salzenberg, Free-Floating Agent of Chaos

Re: SIGINT in system() with shell metachars
by sgifford (Prior) on Jun 25, 2003 at 06:24 UTC
    Here's a hackish way to do it for the pipeline /bin/true |sleep 60 (nonsensical, but easy to kill -INT)...
    system("/bin/true |sleep 60; kill -`expr \$? / 64` \$\$"); print "sysret: $?\n";

    The shell runs the pipeline, calculates what signal the process died from, and kills itself with that signal. If the process didn't die from a signal, it kills itself with signal 0, which does nothing.

Re: SIGINT in system() with shell metachars
by IlyaM (Parson) on Jun 25, 2003 at 16:03 UTC
    Try IPC::Run (in general everytime you think you need a better interface to external program execution then offered by Perl builtins you should use IPC::Run).

    # UNTESTED CODE use IPC::Run qw(start); my $h = start([qw(prog1 file1)], '|', [qw(filter -v), $version], "> $tmpfile", "2>&1"); $h->finish; # returns exist values of each child process which you can use # to find signal that killed each child process my @results = $h->full_results;

    --
    Ilya Martynov, ilya@iponweb.net
    CTO IPonWEB (UK) Ltd
    Quality Perl Programming and Unix Support UK managed @ offshore prices - http://www.iponweb.net
    Personal website - http://martynov.org

Re: SIGINT in system() with shell metachars
by k2 (Scribe) on Jun 25, 2003 at 05:56 UTC
    Why no use qx ?
    $sysret = qx(prog1 file1 | filter -v $version > $tmpfile 2>&1);
    Though I haven't tested the code I use this way of
    executing shell-commands in some of my own code so it
    should work... (I hope ;-) )

    /k2
      It will not work. qw() is just another syntax for backtics (see perlop) which don't return process status but rather return process output.

      --
      Ilya Martynov, ilya@iponweb.net
      CTO IPonWEB (UK) Ltd
      Quality Perl Programming and Unix Support UK managed @ offshore prices - http://www.iponweb.net
      Personal website - http://martynov.org

Re: SIGINT in system() with shell metachars
by cadphile (Beadle) on Jun 25, 2003 at 16:59 UTC
    Thanks to the many monks who replied to my post and query. Sleeping on a problem always is good for new insight, and this morning, seeing your many interesting and good suggestions has been very helpful.

    Some thoughts and comments and conclusions:

    1. The first thing is that the shell (in Solaris at least) encodes the caught signal in its return code: I haven't yet seen this documented in Solaris manuals anywhere, but it's obvious that this is happening. Hence the signal encoded in the HIGH byte (i.e. the return status code).

    2. The backtick method of calling the shell command might actually be better for my needs here, because if you test this out, when I interrupt my Perl script doing a long shell command using backticks, the Perl script catches the signal, whereas if this is done using the system command, the signal is not caught, except through extra handling of the return codes. Since I need to die when interrupted doing shell commands, the backtick method is probably better.

      The other reason why I was using the system command, is that I wanted to catch the full return codes, AND also capture the output (which I did to a tmp file, and then read the tmp file into a variable, and passed both the return status, and the shell output as output to a wrapper System() subroutine. But $? contains the status of the backticked shell command as well as of the system command, and the shell's output goes to a variable, so in effect I can do the same. And since I won't need to dissect the return status to see if it died from a signal (because Perl catches the signal), it should all be sufficient for my needs.

    3. I like the suggestion to use the shell's trap command. Yes! a very under utilized feature of the shell.

    4. Thanks for the suggestion to use IPC::Run. I'll look at that.

    5. Finally, additional research pointed to the possibility of using the open command and piping the output:
      defined($pid = open(CHILDPROC, "$cmdargs |")) or die "Can't fork: $!"; while(<CHILDPROC>){ # ... }

    Thanks again for all the advice.

    cheers!
    -cadphile

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://268704]
Approved by VSarkiss
Front-paged by broquaint
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others drinking their drinks and smoking their pipes about the Monastery: (5)
As of 2024-04-25 12:23 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found