Linux Exit Codes Explained: $?, Status Codes, &&, ||, and Shell Scripts

Transparent crystal switch directing a turquoise sphere toward two paths, representing Linux command success and failure exit codes. Linux & Administration
Linux control flow

A command can print reassuring output and still fail. Another command can finish silently and succeed. The shell does not decide from the words on the screen. It reads a small numeric result called an exit status.

Command runs Work happens
Status returns One number: 0–255
Shell decides Continue, recover, or stop
Quick Answer

Every command returns an exit status when it finishes. In Bash, 0 means success and any non-zero value means the command did not complete successfully. The special parameter $? contains the status of the most recently completed foreground command.

Operators such as && and ||, conditional statements such as if, and automation tools use that status to decide what should happen next.

What You’ll Learn

  • What an exit code tells the shell.
  • How to inspect a result with $?.
  • Why zero means success in Unix-like systems.
  • How && and || react to success and failure.
  • How pipelines affect the status you receive.
  • How scripts return useful results to their callers.

What Is a Linux Exit Code?

An exit code, more precisely called an exit status, is the numeric result a command returns when it finishes. It travels separately from the command’s standard output and standard error.

That separation matters. Output is information for a person or another program to read. An exit status is a compact decision signal for the shell. If standard streams are unfamiliar, read Linux Standard Streams Explained first.

0 Success The command reports that it completed successfully.
1+ Failure or another non-success result The exact meaning belongs to the command that returned it.
$? Most recent status Bash exposes the last foreground pipeline’s result here.

A non-zero result does not always mean that the computer is broken. A search command may use one status for “a match was found,” another for “no match was found,” and another for an actual error. The command’s documentation defines those meanings.

Human-friendly rule: treat 0 as “yes, that worked.” Treat a non-zero value as “do not assume success; inspect what this command says the value means.”

How to Check an Exit Code with $?

Run a command, then print $? immediately:

true
printf '%s\n' "$?"

false
printf '%s\n' "$?"

The true builtin returns 0. The false builtin returns a non-zero status. They perform almost no visible work, which makes them useful for learning control flow.

Read the status before another command replaces it

$? is temporary. Each new foreground command produces a new status, including the command you use to display the previous one.

false
status=$?
printf 'The saved status is %s\n' "$status"

Saving the value in a variable preserves the result you care about. Quoting "$status" is a good habit when expanding shell variables. For the wider relationship between variables and child programs, see Linux Environment Variables Explained .

Common trap: if you run a diagnostic command before saving $?, you inspect the diagnostic command’s result instead of the original result.

What Common Exit Statuses Mean

The range available to Bash is 0 through 255, but there is no universal dictionary for every value. Programs are free to document their own statuses. A few values have special meaning in Bash itself.

Status General meaning What to remember
0 Success The shell treats the command as successful.
1 Often a general failure or a command-specific result Check that command’s manual; do not assume one universal meaning.
2 Incorrect use for Bash builtins; other tools may define it differently Context and documentation still matter.
126 Command found but could not be executed Inspect permissions, file format, and the execution environment.
127 Command not found Check spelling and PATH.
128 + N Command ended because of fatal signal N Use the signal number to investigate how the process ended.

For example, status 127 can point toward a missing command or a PATH problem, while a signal-derived status may connect the failure to process control. The lessons on the Linux shell and Linux processes and signals provide the surrounding model.

How && and || Use Exit Status

These operators do not inspect output text. They decide whether to run another command by reading the status of the command on their left.

Form When the right side runs Plain-language reading
first && second Only if first succeeds Do the second step after a successful first step.
first || second Only if first returns non-zero Use the second step as a failure path.
! command The command runs normally, then its logical result is inverted Treat success as false and failure as true.
mkdir -p demo && cd demo

test -f settings.conf || printf 'settings.conf is missing\n'

In the first line, cd demo runs only if the directory operation succeeds. In the second, the message appears only when the file test returns non-zero.

Do not use && and || as a casual replacement for every if

Short chains are readable. More complicated decisions become clearer with an explicit conditional:

if test -r settings.conf; then
  printf 'Configuration is readable\n'
else
  printf 'Configuration is missing or unreadable\n' >&2
fi

The code after then runs when the test returns 0. The else branch runs when it returns non-zero. This is the bridge from individual commands to understandable shell scripts.

Exit Status and Pipelines

A pipeline connects the output of one command to the input of another:

printf '%s\n' alpha beta gamma | grep -q beta

In Bash, the status of a pipeline is normally the status of its last command. Here, the pipeline reports the result from grep -q beta.

That default can hide an earlier failure. If the first command fails but the last command succeeds, the pipeline may still return success. Bash provides the pipefail option when a script needs earlier pipeline failures to affect the result:

set -o pipefail
producer | processor | consumer
status=$?

With pipefail disabled, the last command determines the status. With it enabled, a failing command earlier in the pipeline can make the pipeline fail. Read the complete stream model in the lesson on redirection and pipes .

Be deliberate with shell options: pipefail changes how a script interprets pipelines. Enable it because your script’s error policy requires it, not because it appeared in a copied snippet.

How Scripts Return a Result

A shell script is itself a command from its caller’s point of view. If the script reaches its end, its status is normally the status of the last command it executed. The exit builtin lets the script report a deliberate value.

if test ! -r settings.conf; then
  printf 'Cannot read settings.conf\n' >&2
  exit 1
fi

printf 'Configuration check passed\n'
exit 0

This creates a useful contract:

  • Human-readable context goes to standard output or standard error.
  • The exit status tells the caller whether the operation succeeded.
  • The script’s documentation explains any distinct non-zero statuses it uses.

Functions use return

A Bash function can use return to send a status back to the code that called it. Without an explicit return, the function normally uses the status of its last command.

config_is_readable() {
  test -r "$1"
}

if config_is_readable settings.conf; then
  printf 'Ready\n'
fi

A shell function’s return value is a status, not an arbitrary text value. If a function needs to produce data, it can write that data to standard output while still using its status to report success or failure.

Mini Lab: Watch the Shell Make Decisions

Safe practice

This lab creates a temporary directory, works only inside it, and removes it when you finish.

1
Create a temporary workspace.
SA_EXIT_LAB=$(mktemp -d)
cd "$SA_EXIT_LAB" || exit 1
printf 'Workspace: %s\n' "$SA_EXIT_LAB"
2
Observe success and save it immediately.
printf 'server=ready\n' > app.conf
status=$?
printf 'Create status: %s\n' "$status"

You should see 0 because the redirection and command succeeded.

3
Observe a predictable non-zero result.
test -f missing.conf
status=$?
printf 'Missing-file test status: %s\n' "$status"

The test returns non-zero because the file does not exist. That is a useful false result, not a system malfunction.

4
Let status control the next command.
test -r app.conf && printf 'Readable\n'
test -r missing.conf || printf 'Not readable\n'
5
Clean up after confirming the path.
printf 'Removing: %s\n' "$SA_EXIT_LAB"
cd /tmp || exit 1
rm -r -- "$SA_EXIT_LAB"
unset SA_EXIT_LAB

Common Exit Code Mistakes

Reading $? too late Another command has already replaced the status. Save it immediately when you need it.
Treating every non-zero value as identical Non-zero means non-success to the shell, but the specific value can carry command-specific meaning.
Parsing friendly output instead of checking status Output can change with options, versions, or locale. Use the command’s documented status interface.
Assuming a pipeline reports every failure Bash normally reports the last command’s status unless pipeline behavior is deliberately changed.
Writing unreadable command chains Use if when a decision needs multiple actions or a clear explanation.
Returning success after a failed script step Decide how failures propagate and end the script with a status that accurately describes the outcome.

Knowledge Check

1. What does exit status 0 mean to Bash?
It means the command or pipeline succeeded. Bash uses that success result in conditionals and command lists.
2. Why must you inspect $? immediately?
Because every new foreground command produces another status. If you wait, $? may describe a later command instead of the command you intended to check.
3. When does the right side of && run?
Only when the command or list on the left returns status 0.
4. When does the right side of || run?
When the command or list on the left returns a non-zero status.
5. Which command normally determines a Bash pipeline’s exit status?
The last command in the pipeline normally determines it. Enabling pipefail changes how failures earlier in the pipeline affect the result.
6. What is the difference between function output and a function return status?
Output is data written through a standard stream. A return status is the numeric success or failure signal passed back to the caller.
Servers Academy — Key Takeaway

An exit code is the shell’s decision signal. A command returns 0 for success and a non-zero status for another outcome. Bash exposes the latest result through $? and uses it to drive &&, ||, if, pipelines, functions, and scripts.

Once you separate readable output from machine-readable status, shell automation becomes easier to understand: commands do the work, streams carry the information, and exit codes decide what happens next.

Official References

Rate article
Servers Academy
Add a comment