Test a command in if
if reads the command's code itself; $? is not needed. The form
cmd; if [ $? -ne 0 ] works until someone puts an echo between the two.
An exit code is a number from 0 to 255 that a command reports: 0 means success, anything else a failure or a "no".
ls /nope; echo $?
2 — code of the last command
grep -q root /etc/passwd && echo found
second runs after code 0
mkdir /srv/app || echo "failed" >&2
second runs after non-zero
if grep -q '^backup:' /etc/passwd; then echo found; fi
rc=$?
save the code before it's lost
exit 1
leave a script with code 1
false | true; echo "${PIPESTATUS[@]}"
1 0 — code of every stage
set -o pipefail
a failed stage fails it all
When a command finishes, it hands the shell an
exit code — an integer from 0 to 255. Zero means success;
any other number means a failure or the answer "no". The code is not printed:
it sits in the $? variable until the next command runs.
The error text is for people and goes to standard error.
Programs read only the code: if, &&, ||, while, systemd, cron and
monitoring.
| Code | Meaning |
|---|---|
| 0 | success |
| 1 | general failure; for grep "no match", for diff "files differ" |
| 2 | usage error: ls found no such path, grep could not open a file |
| 124 | timeout stopped the command |
| 126 | the file exists but cannot run: no x permission |
| 127 | command not found |
| 128+N | killed by signal N: 130 is Ctrl+C, 137 is kill -9, 143 is kill |
A code of 128+N means a signal number N ended the process.
Small codes like 1 and 2 mean different things for each program; the EXIT
STATUS section of its man page lists them. For curl, 6 is "could not
resolve the host", 7 is "could not connect", 28 is "timed out", and 22 is an HTTP response of 400 or above when
-f is given.
a && b runs b only if a returned 0. a || b runs b only if a
returned non-zero. The whole line returns the code of the last command that
ran. A ! before a command swaps success and failure: ! true gives 1.
A script exits with the code of the last command it ran unless it hit
exit N first. A bare exit passes on the previous command's code. Codes wrap
modulo 256: exit 256 gives 0, exit 300 gives 44.
A pipeline returns the code of its last stage, so
false | true returns 0. The codes of all stages are in the PIPESTATUS
array. After set -o pipefail a pipeline returns the code of the rightmost
stage that failed, and 0 only if every stage succeeded.
if reads the command's code itself; $? is not needed. The form
cmd; if [ $? -ne 0 ] works until someone puts an echo between the two.
Every command overwrites $? — echo, [ ], an assignment with a
substitution. If you need the code twice, store it in a variable right away.
The message goes to standard error (>&2) and the code is non-zero, so both a
person and the calling program understand the script.
curl's code answers whether an exchange with the server took place. A 503
response is an exchange too, so the code is 0. The -f option turns responses
of 400 and above into a failure (code 22), while 204 and 302 still count as
success. This is how a test server answered:
| Server response | curl -s |
curl -sf |
curl -s -w '%{http_code}' |
|---|---|---|---|
| 200 | 0 | 0 | 200 |
| 204, 302 | 0 | 0 | 204, 302 |
| 404, 503 | 0 | 22 | 404, 503 |
| port closed | 7 | 7 | 000 |
When you need the exact response code, print it with -w '%{http_code}' and
compare it in a test. With -o and without -s, curl draws
a progress meter on standard error.
The usual convention: 1 means the check failed, 2 means the script was called wrongly. Stay below 126: 126, 127 and 128+N already carry a meaning in the shell.
The check reads the code of the wrong command:
echo OK ran after curl and put its own zero into $?. Test the command
itself: if curl -sf …; then echo OK; fi.
A script returns the code of its last command. This is the journal of a service
(journalctl -u clean.service -o cat) whose script failed on its second line
while the last command (find) succeeded:
Add || exit 1 after the commands that matter, or set -e at the top of the
script (see bash-scripting).
The same cause from the other side. A last line like
[ "$DEBUG" = yes ] && echo "done" returned 1 because DEBUG was empty, and
the script returned 1 with it, even though the work was done. A service that
runs such a script ends up failed. Finish the script with an explicit
exit 0 or rewrite the line with if.
command not found: the command is not in PATH. A script that works by hand
but fails from cron or a timer usually hits their shorter PATH (see
the timer environment). Use the full path to the
program.
The file was found but cannot run: bash: ./check.sh: Permission denied. The
x permission is missing (chmod +x check.sh) or the filesystem is mounted
noexec.
A signal stopped the command: 130 is Ctrl+C, 143 is kill or
systemctl stop, 137 is kill -9, timeout -s KILL or running out of
memory. Look for the sender in the journal:
sudo journalctl -k | grep -i oom.
grep returns 1 when nothing matches. set -e and pipefail treat that as a
failure and the script exits. If "no match" is a normal outcome, write
grep … || true or test it in an if.
c also runs when b fails:
Where it must be one or the other (delete or report), write
if a; then b; else c; fi.
exit $errors with 256 errors returns 0 — success. The code is not a counter:
exit with 1 on any error and print the count.
set -euo pipefail, conditions and loops in scripts.[ ] and [[ ]] tests, which also answer with a code.>&2 and pipelines.systemctl status.$? after an extra command and cd without || exit.Found an inaccuracy? Report an error in the article