11

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

3 Answers 3

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"
0
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
}
2
  • You can also use the code in this format. <pre>function try {
    – BillThor
    Mar 7, 2011 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, 2011 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.

7
  • 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. Mar 7, 2011 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. Mar 8, 2011 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, 2014 at 7:46
  • @ShawnJ.Goff I prefer to do command && true. Like that the return value is not changed.
    – kmkaplan
    Feb 5, 2014 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, 2019 at 4:32

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .