DEV Community

Juma Evans
Juma Evans

Posted on

Bash Exit Codes: How Scripts Know When Something Fails

Have you ever run a command in Linux and wondered how the system knows whether it succeeded or failed?

Sometimes a command produces the expected output. Other times, it prints an error or appears to do nothing. But how can another command determine what happened?

The answer is exit codes.

Exit codes are small numerical values that commands return when they finish executing. In Bash scripting, they help us detect failures, make decisions, and control what happens next.

Let's explore how they work and why they matter.

1. What is an exit code?

An exit code, also called an exit status, is a number returned by a command or process when it finishes.

By convention:

  • 0 means success.
  • A nonzero value indicates some form of failure or an exceptional outcome.

For example, run:

echo "Hello, Linux!"
echo $?
Enter fullscreen mode Exit fullscreen mode

The second command prints:

0
Enter fullscreen mode Exit fullscreen mode

Why? Because the echo command executed successfully.

Now try:

ls nonexistent-file
echo $?
Enter fullscreen mode Exit fullscreen mode

The first command reports an error because the file does not exist. The second command prints a nonzero exit status, typically 2 for GNU ls.

The important point is that an exit code communicates the result of a command to the shell or another program.

Different commands may use different nonzero values to describe different outcomes, so a particular number does not always mean the same thing for every command.

2. Understanding $?

In Bash, $? is a special parameter that contains the exit status of the most recently completed foreground command or pipeline.

Consider this example:

mkdir myproject
echo $?
Enter fullscreen mode Exit fullscreen mode

If the directory is created successfully, the exit status is 0.

Now run the same command again:

mkdir myproject
echo $?
Enter fullscreen mode Exit fullscreen mode

If the directory already exists, mkdir normally reports an error and returns a nonzero status.

But there is an important detail: $? changes after every command.

For example:

ls nonexistent-file
echo "Checking command status"
echo $?
Enter fullscreen mode Exit fullscreen mode

The final echo $? reports the status of the previous echo command, not the earlier ls.

If you need to preserve an exit status, save it immediately:

ls nonexistent-file
status=$?

echo "Exit status: $status"
Enter fullscreen mode Exit fullscreen mode

Now the variable status retains the result from ls.

3. Using exit codes with if statements

One of the most useful applications of exit codes is controlling the flow of a script.

Consider this example:

#!/bin/bash

mkdir myproject

if [ "$?" -eq 0 ]; then
    echo "Directory created successfully"
else
    echo "Failed to create directory"
fi
Enter fullscreen mode Exit fullscreen mode

The script checks whether mkdir succeeded.

However, there is a cleaner way to write it:

#!/bin/bash

if mkdir myproject; then
    echo "Directory created successfully"
else
    echo "Failed to create directory"
fi
Enter fullscreen mode Exit fullscreen mode

Why is the second version better?

Bash allows an if statement to execute a command directly and evaluate its exit status. There is no need to check $? separately.

This approach is widely useful when working with commands such as grep, mkdir, cp, and other utilities.

For example:

if grep -q "Bash" notes.txt; then
    echo "The word was found"
else
    echo "The word was not found"
fi
Enter fullscreen mode Exit fullscreen mode

The -q option tells grep to operate quietly without printing matching lines.

If a match is found, grep returns 0. If no match is found, it returns 1.

That means the if statement can decide which message to display based on the search result.

One caution: some commands use nonzero statuses for ordinary outcomes, not just errors. For grep, no matches is a normal result, even though its exit status is nonzero.

4. What does exit 1 mean?

So far, we have looked at exit codes returned by commands. But you can also choose the exit status of your own Bash script.

Consider:

#!/bin/bash

echo "Task completed"
exit 0
Enter fullscreen mode Exit fullscreen mode

The script explicitly exits with status 0, indicating success.

Now consider:

#!/bin/bash

echo "An error occurred" >&2
exit 1
Enter fullscreen mode Exit fullscreen mode

This script writes an error message to standard error and terminates with status 1.

You can use different nonzero values for different failure conditions when appropriate.

For example:

#!/bin/bash

if [ "$#" -ne 1 ]; then
    echo "Usage: $0 <filename>" >&2
    exit 1
fi

if [ ! -f "$1" ]; then
    echo "Error: File not found" >&2
    exit 2
fi

echo "File exists"
exit 0
Enter fullscreen mode Exit fullscreen mode

Here, the script:

  1. Checks whether exactly one argument was provided.
  2. Checks whether the supplied path is a regular file.
  3. Prints a success message if both checks pass.

The exit codes distinguish invalid usage from a missing file. Their meanings are defined by this script; they are not universal meanings for 1 and 2.

The >&2 syntax redirects the message to standard error, which is commonly used for diagnostic messages.

5. Why exit codes matter in automation

Exit codes become especially important when one command depends on another.

Imagine a script that creates a backup directory and then copies files into it.

If creating the directory fails, continuing blindly could cause additional problems.

You can handle that situation like this:

#!/bin/bash

if mkdir backup; then
    echo "Backup directory ready"

    if cp report.txt backup/; then
        echo "Backup completed"
    else
        echo "Backup failed" >&2
        exit 1
    fi
else
    echo "Could not create backup directory" >&2
    exit 1
fi
Enter fullscreen mode Exit fullscreen mode

The script checks each operation before proceeding.

This is a basic form of error handling: detect failure, report it, and stop when continuing would be inappropriate.

In larger scripts, you might also use && to run a command only if the previous command succeeds:

mkdir backup && cp report.txt backup/
Enter fullscreen mode Exit fullscreen mode

The copy runs only if mkdir succeeds.

However, this example will fail if backup already exists, because plain mkdir backup returns a nonzero status in that situation. Depending on your intended behavior, mkdir -p backup may be more appropriate.

6. A few common mistakes

Mistake 1: Checking $? too late.

cp report.txt backup/
echo "Copy attempted"
echo $?
Enter fullscreen mode Exit fullscreen mode

The last command's status is now being checked, not necessarily the copy's status.

Mistake 2: Assuming every nonzero exit code means the same thing.

A nonzero status signals that the command did not return its conventional success status. The precise meaning depends on the command.

Mistake 3: Printing an error but returning success.

A script can print an error message and still exit with 0. If another script relies on its exit status, it may incorrectly assume everything worked.

Make sure your script's exit status reflects the result you want other programs to observe.

Final thoughts

Exit codes are a small but essential part of Linux scripting. They allow commands to communicate their outcomes and give scripts a reliable way to respond.

Once you understand $?, exit, and how if evaluates command success, you can begin writing scripts that do more than execute commands in sequence.

They can detect problems, make decisions, and stop safely when something goes wrong.

A useful script doesn't just perform a task. It also communicates whether that task succeeded.

Top comments (0)