Hello! Let's dive into our next lesson on shell scripting.
In the previous lesson, we learned how to make scripts interactive by accepting command-line arguments and user input. You saw a practical use of exit 1 to terminate a script when the user provided the wrong number of arguments. That was a small preview of today's topic.
This lesson focuses on the learning outcome: Use exit codes to signal success or failure in scripts. An exit code is the small, often-invisible number that every command and script returns upon completion. Understanding and using these codes is fundamental to writing reliable, automated scripts that can be chained together to perform complex tasks. We'll explore what these codes mean, how to check them, and, most importantly, how to set them in your own scripts to communicate their outcome.
1. What Are Exit Codes?
Every time a command finishes running in Linux, it returns an exit code. This is an integer value that signals how the command terminated. The convention is simple but powerful:
- An exit code of 0 means the command was successful.
- Any non-zero exit code means the command failed or encountered an error.
While you don't usually see these codes, the shell uses them constantly. They are the primary way to programmatically determine if an operation succeeded.
To see this in action, let's watch a video that introduces the concept and shows you how to view the exit code of the last command you ran.
How To Write Bash Scripts In Linux - Complete Guide (Part 6 - Exit Codes)
The following video from Learn Linux TV clearly explains what exit codes are and demonstrates how to check them using a special shell variable.
Watch from the beginning until 03:56. Focus on the difference between an exit code of 0 and non-zero values, and how the $? variable is used to display the exit code of the most recently executed command.
As the video explained, you can check the exit code of the last command using echo $?. Let's try it yourself. Open your terminal and run these commands:
-
A successful command:
ls /etc echo $?You should see a
0printed, because the/etcdirectory exists andlswas able to list its contents successfully. -
A failing command:
ls /non_existent_directory echo $?You will see an error message from
ls, andecho $?will print a non-zero number (likely2), indicating failure.
Different non-zero values can signify different types of errors. While you don't need to memorize all of them, being familiar with a few common ones is very helpful for troubleshooting.
Bash command line exit codes demystified
This article from Red Hat, "Bash command line exit codes demystified", provides a good overview of the most common reserved exit codes. You'll see these frequently when working on the command line.
Read the sections titled "Exit status 0", "Exit status 1", "Exit status 2", "Exit status 126", and "Exit status 127". These cover the most common codes you'll encounter.
2. Using Exit Codes for Script Logic
Knowing that a command failed is useful, but the real power comes from making your script react to that failure. This is essential for creating robust automation. For example, a backup script should stop and send a notification if it fails to access the source directory, rather than continuing and creating an empty backup.
This is achieved using conditional logic (like the if statements we've already covered) to check the value of $?.
Checking and Acting on Exit Codes
Let's see a practical example of a script that installs a package and then checks the exit code to report whether the installation was successful.
How To Write Bash Scripts In Linux - Complete Guide (Part 6 - Exit Codes)
This next segment from the Learn Linux TV video demonstrates why exit codes are crucial for unattended scripts and how to use an if statement to check $? and perform different actions based on success or failure.
Watch from 03:56 to 11:16. Pay attention to how the script's logic branches depending on whether the exit code is 0 or not.
A Critical Pitfall: Overwritten Exit Codes
A very common mistake when scripting is checking $? at the wrong time. The $? variable is updated after every single command. Even a simple echo command will be successful (exit code 0) and will overwrite the exit code of a command that failed right before it.
The next part of the video illustrates this problem and how to avoid it.
How To Write Bash Scripts In Linux - Complete Guide (Part 6 - Exit Codes)
Understanding this pitfall is crucial for writing correct error-handling logic. This segment shows how an intermediate command can mask a failure.
Watch from 14:43 to 20:40. The key takeaway is that you must check $? immediately after the command you care about, before any other command runs.
Setting Your Own Exit Codes with exit
So far, we've focused on checking the exit codes of other commands. To fulfill our learning outcome, we need to control the exit code of our own script. This is done with the exit command.
When your script calls exit, it immediately terminates and sets its exit code to the number you provide.
exit 0signals success.exit 1(or another non-zero number) signals failure.
This allows other scripts or automation tools (like cron) to know whether your script ran successfully.

Let's watch the final segment of the video to see how exit is used to explicitly control a script's outcome.
How To Write Bash Scripts In Linux - Complete Guide (Part 6 - Exit Codes)
This segment shows how to use the exit command to force a specific exit code, giving you full control over how your script reports its status.
Watch from 20:40 to the end. Note how exit immediately stops the script and sets the $? value for whatever process ran it.
Test your understanding!
Modify the script you wrote in the last lesson that created files from command-line arguments. Add logic to check if the touch command was successful for each file. If any touch command fails (e.g., due to a permissions error in a directory), the script should print an error message and immediately terminate with an exit code of 55. If all files are created successfully, it should finish normally (which will implicitly result in an exit code of 0).
To test it, try running it on a directory you don't have write permissions for, like /root../create_files.sh /root/testfile.txt
Show answer
Here is a possible solution:
#!/bin/bash
if [ "$#" -eq 0 ]; then
echo "Usage: $0 <file1> [file2] ..."
exit 1
fi
for filename in "$@"; do
touch "$filename"
# Check the exit code of the 'touch' command IMMEDIATELY.
if [ $? -ne 0 ]; then
echo "Error: Could not create file '$filename'. Aborting."
exit 55 # Exit with our custom failure code.
fi
echo "Created file: $filename"
done
echo "All files created successfully."
exit 0 # Explicitly exit with success.
3. An Important Limitation and Best Practices
You might be tempted to use different exit codes for every possible error (e.g., 10 for "file not found", 11 for "permission denied"). While possible, there's a critical limitation you must know.
Exit codes must be between 0 and 255.
They are stored in a single, unsigned 8-bit integer. If you try to exit with a number larger than 255, the value will "wrap around" (using modulo arithmetic), leading to unexpected results. For example, exit 256 results in an actual exit code of 0 (success!), which can cause major problems.
The following video explains this "gotcha" and provides a best practice for handling errors.
Exit Codes in Bash explained! Some gotchas and pitfalls with them. You Suck at Programming #062
This video from the channel 'You Suck at Programming' explains the 8-bit limitation of exit codes and demonstrates why it's a bad idea to use them to count errors. It then presents the standard, robust way of handling failure.
Watch from 02:14 to 06:27. Pay close attention to: (02:14) The explanation of the 0-255 range and what happens when you exceed it. (04:04) The example of a 'bad idea' script that tries to count failures. (05:19) The recommended 'good idea' of using a simple binary flag (exit 1) for any failure.
The best practice shown in the video is the professional standard:
- Use
exit 0for success. - Use
exit 1for any and all failures. - If you need to provide more details about why it failed, print a descriptive error message to the standard error stream (we will cover this in a future lesson).
This binary approach (success/failure) is simple, avoids the 0-255 overflow issue, and is universally understood by other tools and programmers.
Conclusion
You've now learned one of the most important concepts for reliable scripting and automation. By checking and setting exit codes, your scripts can handle errors gracefully and communicate their status effectively to the wider system.
Key Takeaways:
- The Rule: An exit code of
0means success; any non-zero value means failure. - Checking Status: Use
echo $?to see the exit code of the last command. - Conditional Logic: Use
if [ $? -ne 0 ]to create error-handling logic in your scripts. You must check$?immediately after the command of interest. - Signaling Status: Use the
exit <number>command to terminate your script and set its exit code. - The Limitation: Exit codes are restricted to the range
0to255. - Best Practice: For most scripts,
exit 0for success andexit 1for any failure is the most robust and portable approach. Use error messages to provide details, not the exit code itself.
Preview of the Next Lesson:
Now that you're writing more complex and useful scripts, how do you manage them? What if you want to track changes, collaborate with others, or revert to an older version? The next lesson will introduce you to a tool that does exactly that as we learn to "Use basic Git commands to track changes, and interact with a remote repository."