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:
-
0means success. - A nonzero value indicates some form of failure or an exceptional outcome.
For example, run:
echo "Hello, Linux!"
echo $?
The second command prints:
0
Why? Because the echo command executed successfully.
Now try:
ls nonexistent-file
echo $?
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 $?
If the directory is created successfully, the exit status is 0.
Now run the same command again:
mkdir myproject
echo $?
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 $?
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"
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
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
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
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
The script explicitly exits with status 0, indicating success.
Now consider:
#!/bin/bash
echo "An error occurred" >&2
exit 1
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
Here, the script:
- Checks whether exactly one argument was provided.
- Checks whether the supplied path is a regular file.
- 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
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/
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 $?
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)