Exit Codes Explained: What $? Is Telling You

Exit Codes Explained: What $? Is Telling You

Every command returns a number when it exits. Zero means it worked. Anything else means it did not, and the specific number often tells you how.

ls /tmp
echo $?          # 0

ls /nonexistent
echo $?          # 2

$? holds the status of the last command, and it is overwritten by the next one, including by echo itself. Capture it immediately if you need it twice:

some-command
status=$?

Why zero is success

There is one way to succeed and many ways to fail. Making zero the success value leaves 255 codes free to distinguish failures.

rsync uses this well: 1 is a syntax error, 5 is a protocol start failure, 23 is partial transfer due to errors, 24 is source files vanishing during transfer. Distinguishing 23 from 24 matters, because the second is often benign.

The reserved codes

Some codes come from the shell rather than the program:

CodeMeaning
0Success
1General error, the catch-all
2Misuse of a builtin, often bad arguments
126Found but not executable
127Command not found
128+nKilled by signal n
130Killed by SIGINT, meaning Ctrl-C

127 is a typo or a missing binary. 126 means the file exists and lacks the execute bit, which is a permissions problem rather than a missing file.

Signals and the 128 offset

Codes above 128 encode which signal terminated the process:

137 = 128 + 9   SIGKILL
143 = 128 + 15  SIGTERM
139 = 128 + 11  SIGSEGV
130 = 128 + 2   SIGINT

137 is the one worth memorising. A container exiting 137 was killed with SIGKILL, and in almost every case that means it hit a memory limit and the kernel OOM killer took it, or a cgroup MemoryMax was exceeded.

docker ps -a --format '{{.Names}}\t{{.Status}}'
# myapp   Exited (137) 2 minutes ago     <- out of memory

139 is a segfault. Our process signals guide covers the signals themselves.

Pipelines

$? reports only the last command in a pipeline:

false | true
echo $?          # 0, despite the failure

Two fixes. set -o pipefail makes the pipeline fail if any component does, which our bash error handling guide covers. Or inspect each stage:

false | true | false
echo "${PIPESTATUS[@]}"     # 1 0 1

PIPESTATUS must be read immediately, because the next command replaces it.

The grep problem

grep pattern file
echo $?     # 0 found, 1 not found, 2 actual error

Returning 1 for “no matches” is correct and it interacts badly with set -e, which treats non-zero as a failure and exits.

set -e
count=$(grep -c pattern file || true)      # allow the no-match case

Note that 2 means a real error, such as the file not existing, so || true swallows that too. When it matters, check explicitly:

if grep -q pattern file; then
  echo found
elif [[ $? -eq 1 ]]; then
  echo "not found"
else
  echo "grep failed" >&2
fi

Using them

command && echo "worked"           # run on success
command || echo "failed"           # run on failure
command || { echo "failed" >&2; exit 1; }

if some-command; then ... fi       # tests the exit code directly

Note if some-command rather than if [ $? -eq 0 ]. The first is idiomatic and correct; the second is redundant and easy to get wrong by putting something between the command and the test.

Returning them from your own scripts

#!/usr/bin/env bash
set -euo pipefail

readonly E_MISSING_CONFIG=3
readonly E_NO_NETWORK=4

[[ -f /etc/myapp.conf ]] || exit "$E_MISSING_CONFIG"
ping -c1 -W2 example.com >/dev/null || exit "$E_NO_NETWORK"

exit 0

Named constants make the script readable and let callers distinguish failures. Stay below 125 so your codes are not confused with the reserved range.

Frequently Asked Questions

Why is zero success rather than one?

Because there is one way to succeed and many ways to fail. Zero is the single success value and the remaining 255 codes are available to distinguish different failures, which would not work if success were a non-zero number.

What does exit code 127 mean?

Command not found. It is one of the reserved codes the shell produces itself rather than something the program returned, and it usually means a typo or a binary missing from PATH.

What do exit codes above 128 mean?

The process was terminated by a signal, and the code is 128 plus the signal number. So 137 is 128 plus 9, meaning SIGKILL, which on a container usually means the out-of-memory killer or a memory limit.

How do I get the exit code of an earlier command in a pipeline?

The special PIPESTATUS array holds the status of every component. Reading it immediately after the pipeline gives each stage individually, which the single $? value cannot.

Why does grep return 1 when it works fine?

Because grep returns 1 to mean no matches found, which is a legitimate result rather than an error. This trips up scripts using set -e, since the shell treats the non-zero status as a failure and exits.

What exit code should my own script return?

Zero for success and a small non-zero number for each distinct failure you want callers to distinguish. Avoid 1 through 2 having vague meanings, and avoid the reserved range above 125 so your codes are not confused with shell or signal codes.