<aside> 🚦

Module 3 — Exit Codes and Command Chaining. Every command reports success or failure as a number — automation is built on reading that number, and if, &&, CI pipelines, and monitoring all depend on it.

🧠 concept → 🧩 analogy → 🧪 exercise → ✅ expected result (hidden) → 🎯 interview questions (hidden)

</aside>

<aside> 📋

Before you start. You need Modules 1–2: running scripts, and variables with the "$var" quoting reflex. You have already collected exit codes without knowing it — Module 1's Permission denied and command not found failures each left one behind; this module is where you learn to read them. Tools: nothing new. Keep ~/bash-course.

</aside>

Part A — The hidden verdict

A1. Every command ends with a number

<aside> 📖

Official docs: Bash manual — Exit Status

</aside>

When any command finishes — success or disaster — it hands the shell one small number on its way out: the exit status (also called exit code or return code). The convention is absolute and slightly backwards-feeling: 0 means success. Any other number (1–255) means failure. Text output is for humans; the exit status is the machine-readable verdict, and it exists even for commands that print nothing at all.

Bash stores the most recent verdict in a special variable: $?. Read it like any variable — echo "$?" — and you are reading the result of the command just before.

<aside> 🧠

Counter-intuitive: zero meaning "good" is the reverse of most human scoring, and it is deliberate. There is only one way to succeed, but many ways to fail — so success gets the single value 0, and the entire range 1–255 is left free to describe which failure. Related trap for programmers from other languages: in bash's chaining and (later) if, 0 is the "truthy" value — the opposite of Python or JavaScript. If you ever find bash logic reading backwards, this is why.

</aside>

<aside> 🧩

Real-world analogy — the delivery driver's status board. Every driver returning to the depot taps a code into the board: 0 — "delivered." Any other code explains the problem: 1 — "customer absent," 2 — "address invalid"… The dispatcher never re-reads the driver's whole trip log to decide what happens next; one glance at the code is enough to trigger the next action (send again, call the customer, escalate).

Where the analogy stops working. The depot board keeps history. Bash keeps only the latest code — $? is overwritten by every command that runs, including the very command you use to look at it. Part D turns that quirk into a habit.

</aside>

🧪 Exercise 3.1 — see the verdict

The second command fails on purpose.

date
echo "$?"          # verdict on date
ls /nonexistent    # ← fails
echo "$?"          # verdict on that failure

A2. The failure vocabulary: which numbers mean what

<aside> 📖

Official docs: Bash manual — Exit Status · grep(1) — man7.org (its EXIT STATUS section is a model of the convention)

</aside>

There is no universal table of what 1 or 2 means — each program defines its own codes and documents them in its man page (grep is a beautiful example: 0 = found a match, 1 = no match, 2 = real error — three verdicts, three different follow-up actions). But a few numbers are reserved by bash itself, and you have already caused two of them:

Code Meaning You met it in
0 Success — the only success every working example so far
1, 2, … Program-defined failures — read the program's man page ls uses 2 for "cannot access"
126 Found the file, but it is not executable Module 1's Permission denied
127 Command not found at all — the PATH search failed Module 1's command not found
128+N Killed by signal number N (e.g. 130 = Ctrl-C) coming in Module 13

The distinction between 126 and 127 is Module 1's Part D decision tree, now in numeric form — monitoring systems that only see exit codes can still tell "script missing" from "script not executable" by these two numbers alone.

🧪 Exercise 3.2 — collect the reserved codes deliberately