Bash Scripting TutorialsLinux Tutorials

Bash command line exit codes demystified

Exit codes (also called exit status) are how commands and scripts tell you whether they succeeded or failed. This guide explains how to read and set Bash exit codes using $?, demonstrates reserved codes (0, 1, 2, 126, 127, 128+, 130, 255), and shows safe scripting patterns so you can debug reliably. Focus keywords such as bash exit codes and bash exit status appear naturally in the introduction and throughout the article.

Prerequisites

All examples assume a POSIX-compatible system with bash available and a terminal. No special packages are required.

Quick reference: what an exit code is

The exit code is an 8-bit integer (0–255) returned by a process when it exits. By convention, 0 means success and any non-zero value (1–255) indicates some kind of failure or special condition.

How to read the last command's exit status

Use the special shell variable $? immediately after a command to inspect its exit status.

date
Thu Mar  2 10:12:34 UTC 2026

Here we check the exit status produced by the date command:

echo $?
0

Explanation: echo $? prints the exit status of the previously executed command (here date). You should inspect $? before running another command because it is overwritten by each new command.

Examples of reserved exit codes (with commands and outputs)

Below are practical examples that demonstrate common, reserved, and problematic exit statuses. Each example shows the command (why it's used) and the actual output so you can reproduce and interpret results.

Success: exit status 0

Any well-formed command that completes successfully will return 0.

ls ~
.bashrc
.profile
.ssh/

Immediately after:

echo $?
0

Explanation: ls ~ lists files in your home directory. The exit status 0 indicates the command completed without error.

Generic failure: exit status 1

Exit status 1 is a generic “operation not permitted / miscellaneous error” for many utilities or shell builtins. Some errors that print messages still return 1.

let a=1; let b=0; let c=a/b
-bash: let: c=a/b: division by 0 (error token is "b")
echo $?
1

Explanation: Using let performs arithmetic in bash; division by zero is impermissible and produces an error message plus exit status 1.

Permission or syntax problems: exit status 2

Some shells and utilities use 2 for missing files or permission problems; behavior can vary. Here's a permissions example:

ls /root
ls: cannot open directory '/root': Permission denied
echo $?
2

Explanation: Listing /root as an unprivileged user produces a “Permission denied” message; bash returns 2 here (shell-specific). For debugging, run the failing command interactively to see the diagnostic message.

Not executable: exit status 126

If a file exists but is not executable (file mode), the shell returns 126. Create a simple file and demonstrate.

touch blah.sh

(No output for touch — the file was created.)

./blah.sh
-bash: ./blah.sh: Permission denied
echo $?
126

Fix by making it executable:

chmod +x blah.sh

(No output for chmod on success.)

./blah.sh
echo $?
0

Explanation: chmod +x sets execute permission for **blah.sh**. Running an empty executable returns success 0 (a process can exit 0 even if it prints nothing).

Command not found: exit status 127

If the command is not in your $PATH or is misspelled, bash returns 127. For example:

foobar13535
-bash: foobar13535: command not found
echo $?
127

Explanation: Use the full path or ensure the program is installed and reachable via $PATH. Running a script in the current directory without specifying ./ often yields 127.

Out-of-range exit codes and wrapping (example of 261 -> 5)

Exit codes are limited to 0–255. Values beyond this wrap modulo 256. Demonstration using a subshell:

bash -c 'exit 261'
echo $?
5

Explanation: 261 modulo 256 equals 5. To avoid confusion, always use exit codes in the 0–255 range and reserve special codes for shells.

Ctrl-C produces exit status 130

When you interrupt a foreground process with Ctrl-C (SIGINT), the shell reports 130 (128 + SIGINT(2)). Emulate a Ctrl-C situation by showing a long-running command interrupted manually:

sleep 30
^C
echo $?
130

Explanation: Signals are often reported as 128 + signal-number. SIGINT is signal 2, so 128+2 = 130.

Exit status 255: general failure or out-of-range behavior

Some commands or shells use 255 as a generic failure. Behavior can be command-specific.

ip
Usage: ip [ OPTIONS ] OBJECT { COMMAND | help }
       ip -V
echo $?
255

Explanation: The ip utility returns non-zero usage status in some implementations; 255 is sometimes used for general failures. Consult the specific command's man page for details.

How to set exit status in scripts

Scripts should explicitly return meaningful exit statuses with exit N (0–255). Reserve certain values for shell use and avoid colliding with them when possible.

Basic pattern: set and propagate exit codes

cat > exit-demo.sh <<'EOF'
#!/usr/bin/env bash
# exit-demo.sh - show setting and propagating exit codes
grep -q 'example' /etc/hosts
status=$?
if [ $status -eq 0 ]; then
  echo "Found 'example' in /etc/hosts"
  exit 0
else
  echo "Not found"
  exit 11
fi
EOF
#!/usr/bin/env bash
# exit-demo.sh - show setting and propagating exit codes
grep -q 'example' /etc/hosts
status=$?
if [ $status -eq 0 ]; then
  echo "Found 'example' in /etc/hosts"
  exit 0
else
  echo "Not found"
  exit 11
fi

Run the script and observe output and exit code:

bash ./exit-demo.sh
Not found
echo $?
11

Explanation: The script captures $? (exit status of grep) to make a decision, then exits with an explicit status. Use small, meaningful codes (e.g., 10–20 for custom error classes) and document them.

Short-circuit operators && and ||

Use command && next to run the next command only if the first succeeds (exit 0). Use command || fallback to run fallback when the command fails (non-zero).

true && echo "success"
success
false || echo "failed"
failed

Explanation: true is a command that exits 0; false exits non-zero. These operators reduce boilerplate in scripts and are idiomatic.

Capturing exit codes from pipelines: PIPESTATUS

When commands are piped, $? holds the exit status of the last command in the pipeline. To get each component’s status use the bash array ${PIPESTATUS[@]}.

pytest | tee pytest.txt
================================ test session starts ================================
collected 3 items

test_example.py ..F
===================================== FAILURES ======================================
________________________ test_example_failure __________________________
...failure details...
============================== 2 passed, 1 failed ===============================
echo "${PIPESTATUS[0]}"
1

Explanation: In the pipeline above, PIPESTATUS[0] is the exit code of pytest. This technique allows you to capture the test runner exit code while still sending output to a file (useful in CI systems).

Verification: best practices when debugging scripts

When a script fails, run the troublesome command interactively to read its diagnostic messages. Use set -e to stop on first failure when appropriate, or handle errors explicitly when partial progress is expected.

set -e

Explanation: set -e makes bash exit on any unhandled non-zero status (behaves like “fail fast”). Use with care—some constructs deliberately expect non-zero statuses.

Troubleshooting and conventions

  • Always check permissions and paths first (common causes of 126/127/2).
  • Stick to exit codes in 0–125 for your own signals; shells and tools reserve higher numbers (125–255) in some contexts.
  • Document any non-standard exit codes your scripts use and prefer meaningful messages on stderr to convey details.
  • Use exit codes consistently: 0 = success, 1 = generic failure, other small integers for specific errors.

Conclusion

Understanding bash exit codes and the bash exit status variable $? makes debugging and automation far easier. Read $? right after the command you care about, avoid out-of-range exit values, and prefer clear, documented codes in scripts. Use PIPESTATUS for pipelines and &&/|| to write concise error-handling logic. Armed with these tools, you can make scripts that fail loudly and clearly, helping you fix issues faster.

Komentariši

Vaša email adresa neće biti objavljivana. Neophodna polja su označena sa *