10

When I read this answer about $? another question comes to mind.

Is there any best practice for how to use $? in bash?


Let's have a example:

We have a linear script and I we would like to know that all the command was executed ok. Do you think it is ok to call a small function (let's call it "did_it_work"), to check the error code and break if it's not.

#!/bin/bash 

function did_it_work {
    code=$1
    if [ "$code" -ne "0" ]
    then
        echo "Error failure: code $code "
        exit 1
    fi
}

dir=some/path

mkdir -p $dir
did_it_work $? 

cd $dir
did_it_work $? 

run_some_command
did_it_work $? 

This approach of course means that I have to manually solve the problem if there is any and rerun the script.

Do you think this is a good idea or is there some other best practice to do this?

/Thanks

15

One common way is:

die() {
    IFS=' ' # make sure "$*" is joined with spaces

    # output the arguments if any on stderr:
    [ "$#" -eq 0 ] || printf '%s\n' "$*" 1>&2
    exit 1
}

then you use it like this:

mkdir -p some/path || die "mkdir failed with status $?"

Or if you want it to include the exit status, you could change it to:

die() {
    last_exit_status=$?
    IFS=' '
    printf '%s\n' "FATAL ERROR: $* (status $last_exit_status)" 1>&2
    exit 1
}

and then using it is a bit easier:

mkdir -p some/path || die "mkdir failed"

When it fails, mkdir will likely already have issued an error message, so that second one may be seen as redundant, and you could just do:

mkdir -p some/path || exit   # with the same (failing) exit status as mkdir's
mkdir -p some/path || exit 1 # with exit status 1 always

(or use the first variant of die above without argument)

Just in case you haven't seen command1 || command2 before, it runs command1, and if command1 fails, it runs command2.

So you can read it like "make the directory or die".

Your example would look like:

mkdir -p some/path || die "mkdir failed"
cd some/path || die "cd failed"
some_command || die "some_command failed"

Or you can align the dies further on the right so that the main code is more obvious.

mkdir -p some/path         || die "mkdir failed"
cd some/path               || die "cd failed"
some_command               || die "some_command failed"

Or on the following line when the command lines are long:

mkdir -p some/path ||
  die "mkdir failed"

cd some/path ||
  die "cd failed"

some_command ||
  die "some_command failed"

Also, if you are going to use the name some/path multiple times, store it in a variable so you don't have to keep typing it, and can easily change it if you need to. And when passing variable arguments to commands, make sure to use the -- option delimiter so that the argument is not taken as an option if it starts with -.

dir=some/path
mkdir -p -- "$dir"         || die "Cannot make $dir"
cd -P -- "$dir"            || die "Cannot cd to $dir"
some_command               || die "Cannot run some_command"
9

You could rewrite your code like this:

#!/bin/bash
function try {
    "$@"
    code=$?
    if [ $code -ne 0 ]
    then
        echo "$1 did not work: exit status $code"
        exit 1
    fi
}

try mkdir -p some/path
try cd some/path
try run_some_command

If you don't actually need to log the error code, but just whether the command succeeded or not, you can shorten try() further like so:

function try {
    if ! "$@"
    then
        echo "$1 did not work"
        exit 1
    fi
}
  • You can also use the code in this format. <pre>function try { – BillThor Mar 7 '11 at 17:40
  • If you use this functionality inline and don't want to return from the true side you can replace return $? with the : builtin. – BillThor Mar 7 '11 at 17:50
8

If you really want to exit on an error and are using Bash, then you should also consider set -e. From help set:

-e Exit immediately if a command exits with a non-zero status.

This of course doesn't give you the flexibility of a did_it_work() function, but it is an easy way to make sure your bash script stops on an error without adding lots of calls to your new function.

  • set -e is useful. There are some commands that return non-zero under normal circumstances (for instance, diff). When I'm using set -e in a script where I'm expecting a nonzero return, I do command || true. – Shawn J. Goff Mar 7 '11 at 18:49
  • 2
    Furthermore, even if you use set -e, you can set an “exception handler” to catch all errors with trap did_it_work EXIT. – Gilles Mar 8 '11 at 18:41
  • 1
    This is part of POSIX, not a bash specific feature. Use it but be aware of some pitfalls mywiki.wooledge.org/BashFAQ/105 – kmkaplan Feb 5 '14 at 7:46
  • @ShawnJ.Goff I prefer to do command && true. Like that the return value is not changed. – kmkaplan Feb 5 '14 at 7:49
  • @kmkaplan Your last comment doesn't make any sense to me. The purpose of command || true is to prevent set -e from exiting the script if command returns a non-zero exit code. It changes the exit code because we need to. The only thing command && true does is run true (return a zero exit code) if `command succeeded (returned a zero exit code) - it's a complete no-op. – tripleee Jun 18 at 4:32

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.