C.W.K.
Stream
Lesson 10 of 10 · published

Exit Codes and $?

~15 min · exit-code, status, errors, shell

Level 0Window Tourist
0 XP0/95 lessons0/14 achievements
0/100 XP to next level100 XP to go0% complete

The number every command leaves behind

Every Unix process exits with an integer in the range 0–255. The shell stores the most recent exit code in $?. By convention, 0 means success, anything non-zero means "something is off — and the number tells you what." This is the foundation of every && chain and every CI pipeline you'll ever debug.

Common codes

  • 0 — success
  • 1 — generic error (catch-all for unspecified failure)
  • 2 — usage error (the user's flags didn't make sense)
  • 126 — command found but not executable
  • 127 — command not found
  • 128 + N — terminated by signal N. Ctrl+C (SIGINT = 2) gives 130; kill -9 (SIGKILL = 9) gives 137.

Most well-written tools document their codes in the man page. grep uses 0 = match found, 1 = no match, 2 = error.

Chaining on success or failure

cmd1 && cmd2 runs cmd2 only if cmd1 succeeded. cmd1 || cmd2 runs cmd2 only if cmd1 failed. cmd1 ; cmd2 runs both regardless. Together they are a tiny if/else: ./test.sh && ./deploy.sh || echo "tests failed".

Returning your own codes

In a script, exit N ends the process with code N. In a function, return N sets $? for the caller. Always pick a non-zero code for failures — never silently exit 0 from an error path or your CI lies to you.

Pipeline exit codes

By default, a pipeline's exit code is the last command's. cat missing.txt | wc -l exits 0 even though cat failed. Add set -o pipefail in your scripts and the whole pipeline fails if any stage failed. The scripting track digs into this with set -euo pipefail.

Code

Inspect $? after every command·bash
ls /tmp; echo "$?"           # 0
ls /no/such/dir; echo "$?"   # 1 or 2
grep zzz /etc/hosts; echo "$?"  # 1 (no match)
false; echo "$?"             # 1
true; echo "$?"              # 0
Chain commands on result·bash
# Run deploy only if tests pass
./test.sh && ./deploy.sh
# Fall back if first fails
ping -c1 server.local || echo 'server unreachable'
# Real if/else
git pull && echo 'updated' || echo 'pull failed'

External links

Exercise

Run false; echo $?. Then run grep zzz /etc/hosts; echo $?. Then Ctrl+C a sleep 60 and check echo $? (130). Now write a one-liner: ./check.sh && echo OK || echo FAIL. Confirm it picks the right branch by making check.sh exit 0 then 1.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.