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
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 | [reply] [d/l] [select] |
|
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
| [reply] |
|
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?
| [reply] [d/l] |
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 | [reply] [d/l] |
|
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().
| [reply] |
|
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 | [reply] [d/l] |
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.
| [reply] [d/l] [select] |
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
| [reply] [d/l] |
Re: SIGINT in system() with shell metachars
by k2 (Scribe) on Jun 25, 2003 at 05:56 UTC
|
$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 | [reply] [d/l] |
|
| [reply] |
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:
- 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).
- 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.
- I like the suggestion to use the shell's trap command.
Yes! a very under utilized feature of the shell.
- Thanks for the suggestion to use IPC::Run.
I'll look at that.
- 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
| [reply] [d/l] |
|
|