How to return exit status codes in Bash

Aug 23, 2023#bash

In Bash, exit codes (also known as return codes or status codes) are numeric values returned by executed commands or scripts to indicate the result of their execution.

These exit codes are often used to determine the success or failure of a command or script, allowing other parts of a program or script to make decisions based on the outcome of the executed command.

Exit status convention

In most programming environments and systems, including Bash, exit statuses are represented using 8 bits, which allows for values from 0 to 255. Here are some common exit codes and their meanings:

Code Meaning
0 Success. The command or script executed without any error.
1 General error. The command or script failed for some unspecified reason.
2 Misuse of shell builtins. The command or script used a shell builtin incorrectly, such as passing an invalid option.
126 Command invoked cannot execute. The command or script is found but is not executable, such as lacking permissions or being a directory.
127 Command not found. The command or script is not found in the PATH variable.
128 Invalid argument to exit. The command or script tried to exit with an invalid argument, such as a negative number or a non-integer.
128+N Fatal error signal N. The command or script was terminated by a signal N, where N is a positive integer from 1 to 31. For example, 130 means the command was terminated by Ctrl-C (SIGINT), and 139 means the command was terminated by a segmentation fault (SIGSEGV).
255 Exit status out of range. The command or script tried to exit with a value greater than 255.

Please note that while these exit codes are commonly used, the specific meanings might vary depending on the context and the application or tool you’re using.

If you try to return an exit status outside the 0-255 range, it will be truncated to 8 bits. For example, if you return 256, it will be interpreted as 0, since 256 mod 256 is 0. Similarly, if you return -1, it will be interpreted as 255, since -1 mod 256 is 255.

This can cause unexpected behavior and errors in your scripts. Therefore, it is recommended to only use values from 0 to 125 for normal exit codes, and reserve values above 128 for special cases such as signals or errors.

The special shell variable $?

The special shell variable $? in bash is used to get the exit status of the last command or the most recently executed process. You can use the $? variable to check if a command executed successfully or not, and take appropriate actions based on the result.

#!/bin/bash

# Try to copy a file that does not exist
cp foo.txt bar.txt

# Check the exit status of the cp command
if [ $? -eq 0 ]; then
  echo "Copy successful"
else
  echo "Copy failed"
fi

# Output:
# cp: foo.txt: No such file or directory
# Copy failed

The cp command failed because foo.txt does not exist, and it returned a non-zero exit status, which was stored in the $? variable. The if statement then checked the value of $? and printed the corresponding message.

You can also use the $? variable in the terminal to check the exit status of any command you run interactively.

$ cat foo.txt
cat: foo.txt: No such file or directory
$ echo $?
1

Using exit command

Calling the exit command within a script will terminate the script’s execution immediately. The exit command is used to indicate that the script should exit, and you can provide an optional exit status as an argument to indicate the reason for the exit.

Here’s the basic syntax of the exit command in a Bash script:

exit [exit_status]
  • If you call exit without providing an exit status, the script will exit with a status of 0, indicating successful execution.
  • If you provide an exit status as an argument (an integer value), the script will exit with that specific exit status. This exit status can be used to convey information about the reason for the script’s termination, such as differentiating between different error conditions.
#!/bin/bash

if [[ "$(whoami)" != root ]]; then
  echo "Only user root can run this script."
  exit 1
fi
echo "doing stuff..."
exit 0

Remember that when the script exits, the exit status is usually available in the special variable $?, which can be used to capture the exit status of the script for further processing if needed.

Using exit or return inside a function

Using exit inside a function will terminate the script, regardless of whether the script is sourced or executed. This is because exit causes the shell to terminate with the specified status. Therefore, it is not recommended to use exit inside a function unless you want to end the entire script.

A better alternative is to use return inside a function, which causes the function or sourced script to exit with the specified status. This way, you can control the flow of your script without terminating it abruptly.

For example, consider the following script, which ends after calling exit inside myfunc, and the last echo statement is not executed.

#!/bin/bash

myfunc() {
  echo "Inside myfunc"
  exit 1 # this will terminate the script
}

echo "Before calling myfunc"
myfunc
echo "After calling myfunc" # this will not be executed

# Output:
# Before calling myfunc
# Inside myfunc

However, if you change exit to return in myfunc, you will see the script continues after returning from myfunc, and the last echo statement is executed.

#!/bin/bash

myfunc() {
  echo "Inside myfunc"
  return 1
}

echo "Before calling myfunc"
myfunc
echo "After calling myfunc"

# Output:
# Before calling myfunc
# Inside myfunc
# After calling myfunc

Using && and || operators

The && and || operators are used to combine commands and execute them conditionally based on the exit status of the previous command.

The && operator is used to execute the second command only if the first command succeeds (returns zero). For example, you can use it to run a backup command only if a test command passes:

# This will create a compressed archive of data.txt only if the file exists.
test -f data.txt && tar czvf data.tar.gz data.txt

The || operator is used to execute the second command only if the first command fails (returns non-zero). For example, you can use it to print an error message only if a copy command fails:

# This will copy source.txt to destination.txt and print "Copy failed" 
# only if the copy command returns a non-zero exit status.
cp source.txt destination.txt || echo "Copy failed"

You can also combine the && and || operators to create more complex conditional commands. For example, you can use them to run a cleanup command only if both a download and an install command succeed:

# This will download, unzip, and delete file.zip only if all three commands return zero.
wget http://example.com/file.zip && unzip file.zip && rm file.zip

Best practices

Checking the exit status of every command, using the special variable $?, which stores the exit status of the last executed command or script. For example, you can use an if statement to check if a command succeeded or failed and take appropriate actions based on the result:

# Run a command
some_command
# Check the exit status
if [ $? -eq 0 ]; then
  # Command succeeded
  echo "Command succeeded"
else
  # Command failed
  echo "Command failed"
fi

Handling errors gracefully, using the trap command, which allows you to execute a command or a function when a signal is received by the shell. For example, you can use the trap command to perform some cleanup actions before the shell exits, regardless of the exit status:

# Trap the EXIT signal and call a function
trap cleanup EXIT

# Define a function to delete a temporary file and print a message
cleanup() {
  rm -f temp.txt
  echo "Bye"
}

Using standard exit codes when possible, which are reserved by Bash and have special meanings. For example, an exit code of 0 means success, an exit code of 1 means general error, an exit code of 2 means misuse of shell builtins, and so on.

Using custom exit codes sparingly, which are defined by you and can have any value from 0 to 255. You should avoid using values that conflict with the standard exit codes or with other commands or scripts that may use them. You should also document your custom exit codes and their meanings for future reference.

Using clear and descriptive error messages, which can help you and others understand what went wrong and how to fix it. You should print your error messages to the standard error stream (stderr) using the >&2 redirection operator.

# Print an error message to stderr
echo "Invalid argument" >&2
# Exit with a custom code
exit 10