Perl: After a successful system call, "or die" command still ends script

16,527

Solution 1

That would be a little confusing, wouldn't? - Leonardo Herrera on Ikegami's answer

Yes, it is confusing that the system command inverts true and false in Perl, and creates fun logic like this:

if ( system qw($command) ) {
    die qq(Aw... If failed);
}
else {
    say qq(Hooray! It worked!);
}

But, it's understandable why the system command does this. In Unix, an exit status of zero means the program worked, and a non-zero status could give you information why your system call failed. Maybe the program you were calling doesn't exist. Maybe the program sort of worked as expected. For example, grep returns an exit code of 1 when grep works, but there were no matching lines. You might want to distinguish when grep returns zero, one, or a return code greater than one. (Yes, I know it's silly to use a system call to grep in a Perl program, but that's the first example I could think of).

To prevent casual confusion, I create a variable that holds the exit status of my system command instead of testing the output of system directly:

my $error = system qw($command);
if ($error) {
    die qq(Aw... It failed);
}
else {
    say qq(Hooray! It worked!);
}

It's completely unnecessary, and people who work with Perl should know that system reverses Perl's definition of true and false, but if I hadn't had my coffee in the morning, I may miss it as I go over someone else's code. Doing this little step just makes the program look a bit more logical.

The Perldoc of system give you code that allows you to test the output of your system command to see exactly what happened. (If there was an error, or a system interrupt signal killed the system call). It's nice to know if you need to interrogate your system return value to figure out what went wrong.

Solution 2

system returns the exit status of the command it calls. In shell, zero exit status means success. You have to invert the logic:

0 == system qw(mkdir -p Purged) or die "Failed to create the dir\n";

Solution 3

system returns 0 on success, so you want and rather than or.

See also: use autodie qw( system );

Solution 4

To add what hasn't been mentioned but what comes with it and can quietly mess things up.

The system's return of 'zero-or-not' only tells whether the command itself executed successfully, by shell or execvp; the 0 does not mean that the command succeeded in what it was doing.

In case of non-zero return you need to unpack $? for more information; see system for how to do this. For one, the command's actual exit code is $? >> 8, what the executed program was designed to communicate at its exit.

Altogether you may want to do something to the effect of

sub run_system {
    my ($cmd, @other_agrs) = @_;             # may do more via @other_args

    my ($cmdref, $sys_ret) = (ref $cmd, 0);  # LIST or scalar invocation?
    if ($cmdref eq 'ARRAY') {
        $sys_ret = system(@$cmd);
    }
    elsif (not $cmdref) {
        $sys_ret = system($cmd);
    }
    else { Carp::carp "Invocation error, got $cmdref" }

    return 1 if $sys_ret == 0;

    # Still here? The rest is error handling.
    # (Or handling of particular non-zero returns; see text footnote)
    Carp::carp "Trouble with 'system($cmd)': $?";
    print "Got exit " . ($? >> 8) . " from $cmd\n";
    return 0;  # or Carp::croak (but then design changes)
}

This can be done merely as system == 0 or do { ... }; or such right in the code, but this way there's a little library, where we can refine error handling more easily, eventually decide to switch to a module to manage external commands, etc. Also, being a user-level facility now it makes sense that it returns true on success (we aren't changing system's design).

Additionally in this case, mkdir -p is meant to be quiet and it may not say anything when it cannot do its thing, in some cases. This is by design of course, but one should be aware of it.


A program may return whatever it wants; nothing requires of it to return zero if it succeeded (completed without error) and it does not have to follow any conventions; what it returns is entirely up to its design.

Typical Unix programs generally follow the convention of returning non-zero only if there were problems, but some do return non-zero values merely to indicate a particular condition. See for example here and here.

One example: for at least some versions, zip returns non-zero (12) when it "has nothing to do" (nothing to update in a package for example), even though it completes successfully; so we get 3072 from system in that case (when that 12 is packed into high bits of a number which is the return code). This is documented on its man page.

A zero return, though, normally does imply a successful operation, what is the main point here.

Share:
16,527
FintleWoodle
Author by

FintleWoodle

Updated on June 16, 2022

Comments

  • FintleWoodle
    FintleWoodle almost 2 years

    I am using the following line to make a simple system call which works:

    system ("mkdir -p Purged") or die "Failed to mkdir." ;
    

    Executing the script does make the system call and I can find a directory called Purged, but the error message is still printed and the script dies. What is wrong with my syntax?

  • crazy_in_love
    crazy_in_love about 10 years
    That would be a little confusing, wouldn't?
  • Plaute
    Plaute about 5 years
    That's fine! Simple code is always the best solution :)
  • brian d foy
    brian d foy over 3 years
    system doesn't really reverse anything, and that's not a good way to think about it. The system command returns something and you should understand the exit values of the program and know what to check. The system does not return a value that indicates if the command worked, and some are "successful" but have non-zero exit values.
  • brian d foy
    brian d foy over 3 years
    This isn't really true. system returns the exit value of the program. In Unix, most of the time, programs return 0 to mean success and non-zero for other things. However, some programs use non-zero exit statuses to mean other types of success. system is not judging or interpreting: it's passing through.
  • zdim
    zdim over 3 years
    Edited to state that some programs may return non-zero even if they ran without error, merely to indicate a particular condition: added a footnote, a parenthetical remark, and a line of comment in code. This does not change the point of this answer at all.