As an experienced full-stack developer and sysadmin, exit codes became an indispensable part of my toolbox years ago. While many developers simply use them to check for overall failure, mastering exit codes can radically transform how you script on Linux. Let‘s dig into bash exit codes in-depth so you can level up!
What Are Exit Codes?
First, a quick primer if you‘re unfamiliar with this concept:
An exit code outputs an integer when finishing programs or shell scripts:
$ my_script.sh
# Runs and finishes with exit code
By convention:
- Code 0 signals overall success
- Non-zero indicates an error or failure
Bash stores the most recent foreground exit code in a variable called $?
:
$ unknownCommand
bash: unknownCommand: command not found
$ echo $?
127
Here the shell couldn‘t find unknownCommand
, setting $?
to 127 upon exiting.
Use exit codes synchronously to react to results or asynchronously in logs for debugging.
Standard Exit Codes
While scripts output any integer 1-255, many standard conventions emerged:
Code | Meaning |
---|---|
0 | Success |
1 | General error (often incorrect usage) |
2 | Shell builtin error |
126 | Command invoked cannot execute (permissions problem or invalid exe) |
127 | "Command not found" error |
128 | Invalid argument passed to exit |
128+n | Fatal error signal "n" |
For example:
- 128+9 means exit code 137, which represents a process terminating due to SIGKILL
This provides a handy baseline, though even common commands often use specialized codes breaking the "standards". Always check docs!
Command-Specific Codes
Consult individual program manuals for specifics. For example grep
vs ls
:
grep Codes
Code | Meaning |
---|---|
0 | Match found |
1 | No match found |
2 | Syntax or file error |
ls Codes
Code | Meaning |
---|---|
0 | Success |
1 | Operation not permitted |
2 | No such file or directory |
3 | I/O error |
4 | Internal failure |
As you can see, status codes give more insightful data than just binary success/failure.
Setting Exit Codes in Scripts
The exit
command explicitly sets return status in bash scripts (overriding defaults bash would set):
#!/bin/bash
if [[ ! -d "$1" ]]; then
echo "Error: Input dir $1 doesn‘t exist"
exit 1
fi
# Rest of script
exit 0
This signals a specific failure if the input directory doesn‘t exist, vs overall success upon normal completion.
Trapping Errors Gracefully
While exit
explicitly sets status returns, trap
allows handling errors smoothly within a script rather than terminating immediately:
trap ‘echo "Error on line $LINENO"‘ ERR
someCommand # Triggers an error
echo "This runs in spite of the error"
We can also chain commands using &&
and ||
to conditionally execute logic based on exit codes:
step1 && step2 || handle_error
Only step2
runs if step1
succeeds. Otherwise branch to handle_error
.
These constructs allow scripts to react after things go wrong before finishing.
Portability Considerations
While exit codes work across *nix platforms, beware differences integrating with other environments:
- Windows uses
%ERRORLEVEL%
instead of$?
- Older DOS shells often used 1-10 for base errors
- Luckly Cygwin and newer shells standardize this more cleanly on Windows
If writing cross-platform scripts, test exit code behavior and log the output rather than relying on codes being consistent. Target simpler usage focusing on overall failure rather than granular coding.
Exit Code Best Practices
Now that you understand exit codes more thoroughly, let‘s discuss some best practices leveraging them:
Granular Coding
Rather than manually checking $?
:
if [[ $? == 0 ]]; then
echo "Success!"
fi
Explicitly set codes on failure conditions throughout long scripts:
if [[ $1 == "" ]]; then
echo "Error: Input parameter required"
exit 2
fi
# Rest of script
exit 0
This documents exactly what failed later during debugging.
Tracing Failures
Log traces when branching based on status:
input=$1
validateInput && processInput || handleValidationError
# Log failure
handleValidationError() {
logger "Input $input failed validation"
exit 1
}
Reviewing trace logs later simplifies understanding script execution flows.
Annotating Issues
Comment special exit codes used internally at the top of scripts:
#!/bin/bash
#
# Special Codes:
# 1 - Invalid Parameters
# 2 - File I/O Errors
# 3 - Database Unavailable
# Rest of script...
exit 0
This serves as self-documentation for your future self or other engineers.
Example Exit Code Usage
Let‘s explore a sample script showcasing common exit code patterns:
#!/bin/bash
#
# Special Codes:
# 1 - Input Errors
# 2 - AWS CLI Failure
# 3 - Git Push Failure
REPO=$1
COMMIT_MSG=$2
# Validate Inputs
if [[ $# -ne 2 ]]; then
echo "Usage: $0 reponame commit_msg"
exit 1
fi
# Sync Code to S3
aws s3 sync ./src "s3://mybucket/$REPO" && { echo "S3 sync success"; } || { echo "S3 sync failed"; exit 2; }
# Commit Changes
git add .
git commit -m "$COMMIT_MSG"
# Push Commits
git push origin main || { echo "Git push failed"; exit 3; }
exit 0
This script syncs code to S3 then commits and pushes changes with logic handling failures and coding exits appropriately at each stage.
Notice logging relevant diagnostics before exiting on errors.
Zsh vs Bash Exit Codes
As a full stack developer, you may encounter Zsh in some environments rather than Bash. Does anything differ regarding exit codes?
In essence: no. Zsh adheres closely to Bash and POSIX standards. For example:
% bogusCommand
zsh: command not found: bogusCommand
% echo $?
127
All exit code behavior aligns. But note Zsh does introduce new failure code complexity in intricacies like:
- New signals numbers
- Configuring SIGPIPE behavior
- Process substitution hands signals differently
So while codes work consistently, under the hood additional Zsh nuance exists in some signal handling.
Just take care if mixing Zsh idioms into legacy Bash scripts. Test for portability.
Final Thoughts
I hope this guide provided an expert-level overview into leveraging exit codes effectively!
Mastering exit code usage takes time. Implement gradually and enable traces for insight into script flow failures.
Soon creatively coding exits will become second-nature, allowing incredibly resilient automation!
Let me know if you have any other questions about wielding exit codes like a Linux wizard!