Capturing exit status from STDIN in Perl

I have a perl script that is run with the following command:

/path/to/binary/executable | /path/to/perl/script.pl

      

The script makes an output payload for the binary and then exits after STDIN completes (<> returns undef). This is all well and good, except when the binary outputs are non-zero. From the script POV, it thinks the script just finished cleanly, and so it clears up and exits, with code 0 .

Is there a way for a perl script to know what the exit code is? Ideally, I would like something like this to work:

# close STDIN, and if there was an error, exit with that same error.
unless (close STDIN) {
   print "error closing STDIN: $! ($?)\n";
   exit $?;
}

      

But unfortunately this doesn't work:

$ (date; sleep 3; date; exit 1) | /path/to/perl/script.pl /tmp/test.out
Mon Jun  7 14:43:49 PDT 2010
Mon Jun  7 14:43:52 PDT 2010
$ echo $?
0

      

Is there a way to do this What do I mean?

Edited to add:

The Perl script processes the output of a binary command in real time, so buffering it all to a file is not a valid solution. However, you don't need to know the exit code until the end of the script.

+2


source to share


5 answers


Unfortunately bash is throwing exit status on pipe. Running "sleep 3 | echo hi" triggers an echo before sleep has finished, so it has absolutely no chance of capturing the exit status of the first command.

You could (in theory) run this by changing the bash command to a list of commands - will bash keep the value within $? (like Perl), but then you have to pipe it to the Perl script somehow, which means that your Perl script will have to accept the exit status of the previous program (say) on the command line.



Alternatively, you can simply rewrite the Perl script to run the command and capture the exit status, or wrap the whole thing in another script.

+1


source


The bash environment variable $PIPESTATUS

is an array containing the statuses of each part of the last command pipeline. For example:

$ false | true; echo "PIPESTATUS: ${PIPESTATUS[@]};  ?: $?"
PIPESTATUS: 1 0;  ?: 0

      

So this is rather than refactoring your perl script, you just need the script to run that command to check $PIPESTATUS

. Using $PIPESTATUS

without [@]

gives you the value of the first element of the array.

If you need to check the status of both the initial executable and the perl script, you first need to assign $ PIPESTATUS to another variable:

status=(${PIPESTATUS[@]})

      

Then you can check them separately like



if (( ${status[0]} )); then echo "main reactor core breach!"; exit 1;
elif (( ${status[1]} )); then echo "perls poisoned by toxic spill!"; exit 2;
fi;

      

You have to do it with the temp variable because the next statement, even if it is a statement if

, will be reset ${PIPESTATUS[@]}

to the next statement, even if it is a statement elif

, might check it.

Note that this stuff only works with bash, not the original bourne shell (usually sh

, although many systems link /bin/sh

with /bin/bash

because of its backward compatibility). So if you put this in a shell script the first line should be

#!/bin/bash

      

not #!/bin/sh

.

+8


source


To develop an Ether proposal, this is a wrapper workaround:

bash-2.03$ TF=/tmp/rc_$$; (/bin/false; echo $?>$TF) | 
           perl5.8 -e 'print "OUT\n"'; test `cat $TF|tr -d "\012"` -eq 0 
OUT
bash-2.03$ echo $?
1
bash-2.03$ TF=/tmp/rc_$$; (/bin/true; echo $?>$TF) | 
           perl5.8 -e 'print "OUT\n"'; test `cat $TF|tr -d "\012"` -eq 0     
OUT
bash-2.03$ echo $?
0
bash-2.03$ 

      

Disadvantages:

  • General ugliness

  • Leaves a mess of / tmp / rc _ * files around

  • Will lose the exact value of a non-zero exit code (which is 255 in the above example)

You can deal with all of these disadvantages by slightly changing your Perl script to:

  • Read the contents of a file named $ENV{TF}

    (using File::Slurp::read_file()

    ), say inmy $rc

  • chomp $rc

    ;
  • detachment $ENV{TF}

  • exit $rc

  • Then your command line will be: TF=/tmp/rc_$$; (/bin/true; echo $?>$TF) | /your/perl/script

This is a slightly less invasive change compared to replacing the ether script to use a call system()

- you just add 4 lines to the very end of the script (including exit

); but as soon as you change the script anyway, I would probably recommend getting everything figured out and making the changes live in the first place.

+4


source


I see two options:

  • you can rewrite your script so that it invokes the command itself so that it can detect its exit status and perform different actions if it doesn't exit successfully.
  • you can wrap the command call in a shell script that checked the exit value and then outputted the Perl script differently (essentially the same as option 1, except it didn't need to change the Perl script).

However, since you are reading the input into your Perl script from the command before it exits, you obviously don't have a return code yet. You will only access this after the command completes, so you will need to buffer its output somewhere else, in the meantime, like a file:

use IPC::System::Simple qw(system $EXITVAL);
use File::Temp;

my $tempfile = File::Temp->new->filename;
system("/path/to/binary/executable > $tempfile");
if ($EXITVAL == 0)
{
     system("/path/to/perl/script.pl < $tempfile");
}
else
{
     die "oh noes!";
}

      

+3


source


You only get exit status for your own child processes. The thing related to your STDIN is not a perl child process; it is a shell. So sad you don't want to.

+2


source







All Articles