Dave
Dave

Reputation: 8897

Getting Perl to return the correct exit code

I'm using Perl 5.10.1 on Ubuntu 11.04. I want Perl to execute a shell script and exit with the same code the shell script exits. But this isn't working for me ...

    system($runCmd) or die("Failed to run \"$runCmd\": $!");

I have confirmed that running the "$runCmd" by itself returns an exit code of 255, but the "die" clause isn't getting invoked. How do I exit with the correct code or at least fail for non-success codes?

Another minor requirement is that I want the output from $runCmd to be printed to the screen.

Upvotes: 22

Views: 81020

Answers (5)

YenForYang
YenForYang

Reputation: 3284

I suggest checking the $? variable (a.k.a. $CHILD_ERROR is you use English; pun intended). This allows for a more thorough inspection of how your system() call turns out.

The thing to understand is that $? is a 16-bit word (art credit to @ikegami):

+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| 15| 14| 13| 12| 11| 10|  9|  8|  7|  6|  5|  4|  3|  2|  1|  0|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+

 \-----------------------------/ \-/ \-------------------------/
            Exit code            core     Signal that killed
            (0..255)            dumped         (0..127)
                                (0..1)
system($runCmd);
if ($? == -1) { # Failed to start program / error of the wait(2) system call
    die "Failed to execute '$runCmd': $!";
} elsif ($? & 127) { # Check for non-zero signal
    die "'$runCmd' died with signal", ($? & 127), ($? & 128) ? 'with' : 'without', " coredump";
} else { # No kill signal, check exit code.
    my $exit_code = $? >> 8; # This is the number you said to be 255.
    # print "'$runCmd' exited with value ", $exit_code, "\n";

    if ($exit_code == 255) {
        die("Failed to run \"$runCmd\": $!");
    } else {
        # can do additional checks for other exit codes if desired
    }
    exit $exit_code;
}


There is a noteworthy "alternative" if you really don't want to mess with $?. Basically, just use IPC::System::Simple. Note that it isn't a core module, so you need to install it.

Using it is simple:

use IPC::System::Simple qw(system);  # system() will now die properly

my $exit_value = system($runCmd);
my $exit_value2 = system($runCmd,@args);  # Alternative form that avoids shell if @args

If you don't want to override system you could use run instead:

use IPC::System::Simple qw(run);

my $exit_value = run($runCmd);
my $exit_value2 = run($runCmd,@args);

Upvotes: 2

Linus Kleen
Linus Kleen

Reputation: 34612

If system returns 255, you need an and conditional.

system returns zero on successful execution. Also, die will modify your script's exit code. Instead warn and return the last exit code like this:

system($cmd) and do {
    warn "Failed to run $cmd. Exit code is $?";
    exit $? >> 8;
};

In order to catch the program's output, use the backtick (`) operator:

my $output = `$cmd`;

if ($?) {
   warn ...;
   exit $? >> 8;
}

The backtick operator only captures STDOUT, so for all error messages (which usually go to STDERR) to be captured, modify $cmd and append 2>&1 to it.

Notice the right shift by eight bits on the $? global.


Credits go to @musiKk: The perl documentation on system() states how to properly retrieve the actual exit status.

Upvotes: 13

Dodger
Dodger

Reputation: 140

If system()'s seeming backwardness bothers you, you can always make it more palatable by doing something like:

my $err = system '/something/that/may/not/work';
if ($err) {
    warn "@{[$! || 'undefined error']}\n";
    exit $err >> 8;
}

Your $runCmd, BTW, if it prints to the screen (and this is run from the command line and you haven't redirected the output, etc.) will print to the screen. It's just that IT will print it to the screen. Perl won't be responsible for that or know (or care) what it's printing. If that's all you want, and you don't want to systematically analyse or manipulate the output of $runCmd, you're golden. Try it:

perl -e "system 'ls -Fahl'"

It also won't interfere with your $runCmd's STDOUT either. That'll go to your terminal too. For instance:

$ perl -e "system 'ls -Fahl /dev/null/something' and die qq(fail: $! >> 8 == @{[$! >> 8]})"
ls: /dev/null/something: Not a directory
fail: 26205 >> 8 == 102 at -e line 1.

Upvotes: 0

tchrist
tchrist

Reputation: 80384

  system($runCmd) or die("Failed to run \"$runCmd\": $!");

Unlike most Perl functions, system returns false when it succeeds and true when it fails. This is completely backwards I know but it is just how it is.

You need "system() and" not "system() or". And you probably want $? not $!, although this can sometimes be tricky.

I have a minor aversion to

system(...)                           && die

because it screws up all the rest of || die’s that normally make a continuous vertical double margin over on the right, I sometimes write

system(...) == 0                      || die

so that they all line up correctly again.

Upvotes: 5

user554546
user554546

Reputation:

As perldoc -f die says, die doesn't give a specific exit code (only a nonzero exit code). To get what you want, you'll need something like:

my $exit_code=system($runCmd);

if($exit_code!=0)
{
  print "Command $runCmd failed with an exit code of $exit_code.\n";
  exit($exit_code >> 8);
}
else
{
  print "Command $runCmd successful!\n";
}

Upvotes: 20

Related Questions