| Tested on | RHEL 10.2 (Coughlan) |
|---|---|
| Package | bash 5.2.26-6.el10 |
| Applies to | Linux distributions with Bash installed |
| Privilege | Normal user; no root required for the examples |
| Scope | Bash exit status mental model, $?, exit, testing commands in conditionals, &&, ||, !, common status codes, 128+N signal conventions, pipeline status with PIPESTATUS and pipefail, brief set -e behavior, one-byte wrapping above 255, brief return vs exit, and designing script exit codes. Does not cover full pipeline error handling or complete set -e rules. |
| Related guides | Bash shell script tutorial Shell scripting course hub |
Bash represents command exit statuses as values from 0 to 255. Status 0 means success; Bash treats any nonzero status as failure for conditionals and command chaining. Bash stores the status of the most recently executed command in $?, and scripts can return their own status with exit N.
In shell conditionals, status 0 means success (true) and any nonzero value means failure (false). Once that model is clear, testing commands directly is usually clearer than copying $? into variables.
What Is a Bash Exit Code?
An exit code, exit status, and return status all refer to the same small integer a command hands back when it finishes. Bash uses 0 for success and 1–255 for failure from the shell's point of view. Individual programs decide what their particular nonzero numbers mean.
Bash records the latest status in $?. When you run a script, its final status becomes the status of that script invocation so callers can react with if ./script.sh; then ....
true and false are built-in commands that always return fixed statuses:
true
echo $?0false always returns 1:
false
echo $?1Bash if else treats 0 as the condition passing and any other value as failing.
Check the Last Exit Code with $?
After a real command succeeds, $? is 0:
ls /etc/passwd
echo $?/etc/passwd
0After a failure, $? is nonzero and the command usually prints an error:
ls /no-such-file
echo $?ls: cannot access '/no-such-file': No such file or directory
2$? is overwritten by the very next command. Save it immediately when you need the numeric value later:
ls /no-such-file 2>/dev/null
status=$?
echo "Exit status: $status"Exit status: 2The Bash variable keeps 2 even after later commands succeed.
Check Command Success in an if Statement
When you only need success or failure, run the command inside if and skip $?:
if ls /etc/passwd >/dev/null; then
echo 'passwd exists'
else
echo 'passwd missing'
fipasswd existsCopy $? only when you must log the number, pass it to another program, or branch on a specific code. Any command between the original command and [ $? -eq 0 ] changes $?, which is why if command; then is safer for simple checks.
Set a Script Exit Code with exit
exit ends the current shell or script and reports a status to the caller. bash -c runs a child Bash process; when it exits with 5, your current shell receives the same status:
bash -c 'exit 5'
echo $?5exit 0 vs exit 1
exit 0 tells the caller the script finished successfully. exit 1 (or any other nonzero value) signals failure. The value 1 does not tell the caller why something failed, so use other documented nonzero codes when callers need to distinguish error types.
What Does exit Without an Argument Do?
With no argument, exit returns the status of the last command executed:
bash -c 'false; exit'
echo $?1That pattern is useful in wrapper scripts: run a command, then exit with whatever status it left behind.
Common Bash Exit Codes and Meanings
| Exit status | Meaning in Bash / common convention |
|---|---|
0 |
Success |
1 |
Common generic failure; exact meaning depends on the command |
2 |
Incorrect usage for Bash builtins; other programs may define 2 differently |
126 |
Command found but could not be executed |
127 |
Command not found |
128+N |
Bash reports termination by signal N |
130 |
Common Bash result after Ctrl+C (128 + SIGINT 2) |
137 |
Terminated by SIGKILL (128 + 9) |
143 |
Terminated by SIGTERM (128 + 15) |
Apart from shell-defined cases such as 126 and 127, the meaning of a nonzero status usually depends on the command. For example, grep returns 0 when it finds a match, 1 when no line matches, and a value greater than 1 for an error. Check the command's documentation when you need to interpret a specific code.
Exit code 127 example
A missing command name returns 127:
nosuchcmd_xyz
echo $?bash: nosuchcmd_xyz: command not found
127Exit code 126 example
A file without execute permission returns 126 when you try to run it directly:
printf '#!/bin/bash\necho hi\n' > /tmp/noexec.sh
chmod 644 /tmp/noexec.sh
/tmp/noexec.sh
echo $?bash: /tmp/noexec.sh: Permission denied
126Exit Codes from Signals
Bash reports a command terminated by signal N as 128 + N. This is Bash behavior; POSIX only requires signal termination to produce a status greater than 128.
SIGKILL (signal 9) produces status 137. The kill and pkill guide covers sending signals by PID or process name. Depending on the shell and terminal, Bash may also print a Killed diagnostic. The important value here is the exit status:
bash -c 'kill -9 $$'Read the exit status in the parent shell:
echo $?137SIGINT from Ctrl+C commonly appears as 130 (128 + 2). SIGTERM often appears as 143 (128 + 15). Do not assume every value above 128 was a signal without knowing how the process exited.
Use &&, || and ! with Exit Status
&& runs the right-hand command only when the left command succeeds (0). || runs the right-hand command only when the left command fails. GNU calls these AND and OR lists.
Copy a file only when the destination directory exists:
mkdir -p /tmp/demo-backup && cp /etc/hosts /tmp/demo-backup/hosts&& skips cp when mkdir fails. || runs a fallback when the left command fails:
cd /no/such/path || echo 'cd failed'cd failedWhen ! precedes a command or pipeline, Bash logically negates its exit status: success becomes failure and a nonzero status becomes success.
! false
echo $?0Why cmd && success_action || failure_action is not if/else
If success_action can fail, the || arm may run even when the original command succeeded:
true && false || echo 'failure path'
echo $?failure path
0The command chain returns 0 because the last command that ran, echo, succeeded. Use a full if/else when both branches must handle errors precisely.
Bash Pipeline Exit Codes
By default, a pipeline's exit status is the status of its last command, even when an earlier command failed:
false | true
echo $?0Read every stage with PIPESTATUS:
false | true
echo "${PIPESTATUS[@]}"1 0Enable pipefail so a pipeline returns a nonzero status if one or more stages fail. Bash uses the status of the rightmost command that failed:
set -o pipefail
false | true
echo $?1PIPESTATUS and pipefail have additional behavior worth understanding in Bash pipes and pipefail.
exit vs return
exit ends the current script or shell. Inside a function, return stops the function and sets its status while the script continues.
demo() {
return 5
}
demo
echo "function status=$?"function status=5return can also stop a file executed with the Bash source command or . without terminating the calling shell. By contrast, exit in a sourced file can end the current shell. See Bash functions for argument handling, local variables, and deeper function patterns.
Create Custom Exit Codes in Bash
Document what each code means so callers can branch on the number instead of parsing log text. The sample below checks $# and whether the path is a regular file — Bash script arguments covers positional parameters, and check if file exists in Bash covers -f and related tests:
cat > check-file.sh << 'EOF'
#!/usr/bin/env bash
readonly ERR_USAGE=2
readonly ERR_INPUT=3
if (( $# != 1 )); then
echo "Usage: $0 FILE" >&2
exit "$ERR_USAGE"
fi
if [[ ! -f $1 ]]; then
echo "File not found: $1" >&2
exit "$ERR_INPUT"
fi
echo "Processing $1"
exit 0
EOF
chmod +x check-file.shWrong argument count returns 2:
./check-file.sh
echo $?Usage: ./check-file.sh FILE
2A missing file returns 3:
./check-file.sh missing.txt
echo $?File not found: missing.txt
3Exit codes are limited to 0–255
Bash restricts exit statuses to eight bits. Values above 255 wrap with modulo 256, so exit 300 is reported as 44:
bash -c 'exit 300'
echo $?44For application-defined codes, prefer small values below 126 so they do not overlap with Bash's 126, 127, and signal-related statuses.
Does Bash Exit Automatically on an Error?
No. By default, a Bash script normally continues after a command returns a nonzero status.
set -e (errexit) can stop the shell after many unhandled failures, but it has important exceptions in conditions, &&/|| lists, ! expressions, and pipelines. pipefail also affects how pipeline failures interact with set -e. A dedicated error-handling guide covers the complete rules.
Common Exit-Code Problems
| Symptom | Likely cause | Fix |
|---|---|---|
| Wrong branch after a command | Another command ran before you read $? |
Save $? immediately or use if command; then |
| Pipeline looks successful but a middle command failed | Default pipeline status uses the last command | Enable pipefail and read PIPESTATUS |
Caller sees 44 instead of 300 |
Status wrapped above 255 | Use codes 0–125 or document the wrap |
success_action || failure_action runs unexpectedly |
success_action returned nonzero |
Use if/else instead of &&/` |
| Function error stops nothing | Used return where exit was needed |
exit for script-level failure; return inside functions |
| Script keeps running after a failure | Bash normally continues after nonzero statuses | Handle the command explicitly or use set -e when its behavior fits the script |
Summary
Bash reports whether a command succeeded with a small integer from 0 to 255. Status 0 means success; nonzero means failure in conditionals. Read $? immediately or test commands in if lists so another command does not overwrite the status you care about.
Recognize shell-defined codes such as 127 for missing commands, 126 for non-executable files, and 128+N after fatal signals. Remember that most nonzero values are command-specific. Use exit to hand a final status to callers, return inside functions, and PIPESTATUS or pipefail when pipelines must fail on any stage.
Chain with &&, ||, and ! for short guards, but use a full if/else when both branches must handle errors cleanly. Scripts do not stop on failure unless you enable set -e or check statuses yourself.
References
- Bash manual — Exit Status — how Bash records status, including
126,127, and128+N - Bash manual — Pipelines — last-command status,
pipefail, and!negation - Bash manual —
exitbuiltin — terminating a shell with a status - POSIX Shell Command Language — process exit status, signal termination, and AND/OR lists
Frequently Asked Questions
What does $? mean in Bash?
$? expands to the exit status of the most recently executed foreground pipeline. 0 means success; any other value means failure in conditionals.
What is the difference between exit 0 and exit 1?
exit 0 reports success to the caller. exit 1 reports a generic failure. Other nonzero values can document specific error types when callers know your convention.
What does Bash exit code 127 mean?
Bash returns 127 when it cannot find the command name you typed. Check spelling, PATH, and whether the program is installed.
What do exit codes 126, 130, 137 and 143 mean?
126 means the command was found but could not be executed. 130 commonly follows Ctrl+C (128 + SIGINT). 137 means the process was killed with SIGKILL (128 + 9). 143 often means termination by SIGTERM (128 + 15).
Does Bash automatically exit when a command fails?
No. By default, scripts keep running after a nonzero status. set -e can exit on many failures, but it has exceptions for conditionals, &&, ||, !, and pipelines.
Can a Bash exit code be greater than 255?
Statuses are stored in one byte at the process boundary. Large values wrap; for example exit 300 is seen as 44 (300 % 256).

