1. Overview

Exit status codes refer to the numeric value that a process returns upon completion. Upon successful execution, this value is equal to zero. Any non-zero exit code indicates that some error occurred.

We’re not entirely free to choose what exit codes we use in our scripts and programs; some standard codes have a special meaning. In this tutorial, we’ll examine which codes we can use, the special exit codes, and their meaning.

2. Exit Codes

Exit codes have a minimum value of 0 and a maximum value of 255. This range might seem limited considering they’re technically 32-bit integers. The limitation stems from historical conventions and practical considerations within the Unix and Linux environments.

The restriction to 8 bits (1 byte) allows exit codes to be stored efficiently within a single byte of memory. This facilitates their use in various system calls and simplifies error handling mechanisms. Furthermore, the convention of limiting exit codes to 8 bits ensures compatibility and consistency across different platforms and programming languages.

In shell scripts, we use the exit command to generate exit codes. The exit command takes one argument, the exit code we want to use. For example:

$ bash -c "exit 42"
$ echo $?
42

Customizing exit codes can be useful for:

  • specific error signaling
  • controlling script behavior
  • integration with automation and monitoring

Let’s try an example:

$ cat validate_age.sh         
#!/bin/bash

read -p "Enter your age: " age

if [[ ! $age =~ ^[0-9]+$ ]]; then
  echo "Error: Invalid input. Please enter a number for your age."
  exit 1  # Exit code 1 for invalid input
fi

if [[ $age -lt 18 ]]; then
  echo "You are not eligible to register."
  exit 2  # Exit code 2 for under 18
elif [[ $age -ge 18 && $age -lt 65 ]]; then
  echo "You are eligible to register"
  exit 0  # Exit code 0 for eligible user (18-64)
else
  echo "You may not be required to register, but you are still eligible."
  exit 3  # Exit code 3 for 65+ (eligible but not required)
fi

In this script, we set custom exit codes for different possible outcomes in an age checker program. Subsequently, we can implement different logic based on these codes.

3. Special Exit Codes

In Linux, some exit numbers indicate not only a failure occurred but the reason for the failure. However, some special codes might have different meanings in different shells.

The following examples apply to the Bash shell.

3.1. General Error: 1

Exit code 1 indicates a general error condition or failure. It commonly signifies that the program encountered an unexpected problem or failed to perform its intended operation.

For example, let’s get a command to fail by dividing by 0:

$ echo result=$((10 / 0))
-bash: 10 / 0: division by 0 (error token is "0")
$ echo $?
1

1 is a generic catch-all value for miscellaneous errors.

3.2. Misuse of Shell Built-in: 2

Exit code 2 signifies invalid usage of some shell built-in command. Examples of built-in commands include aliasecho, and printf. Alternatively, it could mean that we’re trying to access a file or directory that doesn’t exist or requires permissions.

Let’s simulate an exit code of 2 with an invalid command argument:

$ ls /nonexistent_directory
ls: cannot access '/nonexistent_directory': No such file or directory
$ echo $?
2

We get the exit code 2 because the directory doesn’t exist.

3.3. Cannot Execute: 126

In this case, the command invoked can’t be executed. This likely occurs when there’s a permission problem or the command isn’t executable.

To see this code in action, let’s suppose we have a non-executable script:

$ cat non_executable_script.sh     
#!/bin/bash

echo hello

First, we ensure it’s not executable:

$ chmod -x non_executable_script.sh

Next, let’s attempt to execute it, and then check out the exit code:

$ ./non_executable_script.sh       
zsh: permission denied: ./non_executable_script.sh
$ echo $?                   
126

We initially receive an error message telling us permission denied, and an exit code 126.

3.4. Command Not Found: 127

A command couldn’t be found. This happens when we try to run a command that doesn’t exist, perhaps, due to a typo or an issue with our PATH.

Let’s make a mistake in spelling to see an instance:

$ ech $?
Command 'ech' not found, did you mean:
  command 'dch' from deb devscripts (2.22.1ubuntu1)
  ...
$ echo $?
127

This output indicates that ech does not exist as a command in the environment.

3.5. Fatal Error Signal n: 128+n

In Linux, programs might send one of 31 different standard signals. When a program terminates after receiving one of these signals, it returns an error code equal to 128 + signal-number. For example, when we terminate a program by using Ctrl+C, we effectively send it a SIGINT signal.

Let’s check the exit code after using Ctrl+C:

$ echo $?   
130

This signal has a value of 2. Therefore, the program will stop executing and return an exit code with a value 128 + 2 = 130.

The shell uses these values to inform the user about how a process was terminated. Reusing them could confuse the users of our applications or scripts.

3.6. Can We Use Special Exit Codes?

As we’ve learned, some exit codes have special meanings. Thus, using them in our scripts might portray a different meaning than intended.

While technically possible to use them, it’s best to avoid confusing users by limiting our error code usage to the general code 1 and custom codes between 3 and 125, avoiding the special exit codes.

4. Exit Codes Above 255 or Below 0

Earlier, we established that a single byte value represents exit codes, and the highest possible exit code is 255. Values over 255 are out of range and get wrapped around. We should be very careful as this can be a source of unexpected results. For example, the exit code of 383 will be wrapped around and result in an effective exit code of 127, which translates to “command not found”:

$ bash -c "exit 383"
echo $?
127

Or the exit code -1 gets translated to 255:

$ bash -c "exit -1"
echo $?
255

But the most dangerous implication is that some values could be interpreted as a successful exit. For example, a user will see zero if we use a 256 exit code:

#!/bin/bash

# Intentionally cause an error (divide by zero)
echo "Attempting division by zero..."
result=$((10 / 0))

exit 256

Now, let’s run the script and afterwards, check out the exit code:

$ bash exceeding_max_value.sh
Attempting division by zero...
exceeding_max_value.sh: line 5: 10 / 0: division by 0 (error token is "0")
$ echo $?
0

We see that there’s an error, yet we get a deceptive value of 0 due to the wrap around behavior of exit codes.

While it’s technically possible to return exit values over 255, it’s always better to avoid it.

5. Conclusion

In this article, we learned that some exit codes are special while we can use others freely in the scripts and programs we write. We also learned how some values could end up misleading users of our applications. Furthermore, we saw that values above 255 could be outright dangerous to the point of hiding an error in a successful exit code.

In addition, we have to remember that different shells might have other special exit codes. This is something to be aware of. Finally, when writing programs or scripts, we should document the exit codes we use as a courtesy to our users.

Comments are open for 30 days after publishing a post. For any issues past this date, use the Contact form on the site.