Skip to content
Lesson 13 of 14

Step 1 of 7 · Reading · ~4 min

Learn

Command Substitution and Exit Status

Exit Status

By the end of this lesson you can read a command's result, tell apart the different kinds of failure a command can report, chain commands on success or failure, and set your own script's result. Every command ends with an exit status, an integer from 0 to 255. 0 means success and anything else means failure, the opposite of truthiness in C or Python. Bash's control flow is built on this one number.

Reading the status

ls /etc            # succeeds
echo "$?"          # 0
ls /nonexistent    # fails
echo "$?"          # 2

$? holds the status of the most recently finished command, and it is overwritten by every command, including the echo you use to look at it. Capture it at once if you need it twice:

cmp -s first.bin second.bin     # -s: print nothing, just report
status=$?
echo "cmp said $status"

Conventions you will meet: 0 success, 1 a general failure, 2 misuse or trouble, 126 found but not executable, 127 command not found, and 128+N for death by signal N (130 is Ctrl+C, 137 is kill -9).

Many tools use more than one failure code to say different things. cmp and diff use 0 for "identical", 1 for "different" and 2 for "something went wrong, such as a missing file". Treating every non-zero status as the same thing merges "the files differ" with "I could not even read them", which are very different news. Look up a tool's codes in its manual before you branch on them:

diff -q old.conf new.conf > /dev/null 2>&1
case $? in
    0) echo "unchanged" ;;
    1) echo "modified" ;;
    *) echo "could not compare" ;;
esac

Setting your own status

#!/usr/bin/env bash
if [[ ! -f settings.ini ]]; then
    echo "missing settings" >&2     # the message goes to stderr
    exit 3                          # the script ends with status 3
fi

A script that reaches its end without exit finishes with the status of its last command. Be explicit when the status matters.

Chaining with && and ||

&& runs the next command only if the previous one succeeded, and || runs it only if the previous one failed:

mkdir out && cp report.txt out/        # cp runs only if mkdir worked
make test && make deploy               # deploy only if the tests pass
command_that_may_fail || echo "failed" # message only on failure
cd /srv/app || exit 1                  # leave the script if the cd fails

A caution about combining them. a && b || c is not an if/else: c runs when a fails and also when a succeeds but b fails. When there is a real choice between two branches, use if:

if grep -q "ready" status.txt; then
    echo "go"
else
    echo "wait"
fi

Use && for short guards where failure is the unusual case, and if when there is an else or more than one command.

Tests: [ ] and [[ ]]

[ is a command (there is a program called [), so its arguments are split like any other and the spaces around it are mandatory. [[ ]] is bash syntax, parsed before expansion, so it does not word-split its operands and it adds pattern and regex matching:

[[ -d "$dir" ]]            # a directory exists
[[ -r "$f" && -s "$f" ]]   # readable and non-empty
[[ "$name" == a* ]]        # glob match
[[ "$mail" =~ @ ]]         # regular-expression match
[ "$n" -gt 5 ]             # the POSIX form: -eq -ne -lt -le -gt -ge

Prefer [[ ]] in bash scripts and keep [ ] for scripts that must run under plain sh. With [ ], quote every variable: [ $x = foo ] becomes [ = foo ] when x is empty, and fails with a syntax error.

Common mistakes

  • Reading $? too late. Any command in between, even an echo, replaces it.
  • Treating all failures alike. A non-zero status can mean "no result" or "could not run"; check the tool's codes.
  • a && b || c as if/else. c also runs when b fails.
  • Expecting 0 to be false. In bash 0 is the success case.
  • Unquoted variables inside [ ]. An empty value disappears and changes the meaning of the test.

Your exercise

Run grep on a line of text and report exactly what it said. Its exit status separates three outcomes, and you must keep them apart and show the number as well as the verdict. Decide where $? has to be read.

Up nextgetoptsCommand Substitution and Exit Status

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…