Bash Error Handling
Managing errors and exit codes in scripts
⚠️ What is Error Handling?
Error handling in Bash helps you detect, manage, and respond to errors in your scripts. It ensures your scripts fail gracefully and provide useful feedback when something goes wrong.
#!/bin/bash
# Check if command succeeded
if [ $? -eq 0 ]; then
echo "Success!"
fi
Key Error Handling Concepts
Exit Codes
Check command success or failure
echo $?
Exit on Error
Stop script when errors occur
set -e
Error Trapping
Catch and handle errors
trap 'echo Error' ERR
Validation
Check conditions before proceeding
[ -f file ] || exit 1
🔹 Exit Codes
Every command returns an exit code indicating success (0) or failure (non-zero). The special variable $? stores the last command's exit status. Checking this code is fundamental for error handling—allowing scripts to react appropriately. For instance, grep "pattern" file.txt returns 0 if the pattern is found, else 1. By evaluating $?, you can branch logic: success might continue processing, while failure could trigger an alert or cleanup. This mechanism is the backbone of reliable script automation and flow control.
#!/bin/bash
# exit-codes.sh
ls /tmp
echo "Exit code: $?"
ls /nonexistent 2>/dev/null
echo "Exit code: $?"
Output:
[files listed]
Exit code: 0
Exit code: 2
🔹 Checking Command Success
Use if statements or logical operators to verify command outcomes directly. Instead of manually checking $?, you can write if command; then .... This structure runs the conditional block only if the command succeeds (exit code 0). For concise checks, the && operator runs a subsequent command only on success, while || runs it on failure. Example: mkdir /backup && echo "Directory created" || echo "Failed". This approach makes error handling explicit, readable, and integrated directly into command execution.
#!/bin/bash
# check-success.sh
if mkdir /tmp/testdir 2>/dev/null; then
echo "Directory created successfully"
else
echo "Failed to create directory"
exit 1
fi
Output:
Directory created successfully
🔹 Set -e Option
The set -e directive causes a script to exit immediately if any command fails. Placed at the script's start, it prevents cascading errors where a failure is ignored, and later commands execute with invalid data. This is a global safety net, reducing the need for individual error checks on every line. For example, if cd /nonexistent fails, the script stops right there. Combine it with set -u (treat unset variables as errors) for even stricter control. It's a simple yet powerful practice for writing robust, fail-fast scripts.
#!/bin/bash
# strict-mode.sh
set -e # Exit on any error
echo "Starting..."
mkdir /tmp/mydir
cd /tmp/mydir
echo "Success!"
Output:
Starting...
Success!
🔹 Custom Exit Codes
Scripts can exit with custom codes (1-255) to signal specific error types. By convention, 0 means success, 1 is a general error, and higher values can indicate distinct issues like missing files (2), permission denied (3), or invalid input (4). Use exit 2 to terminate with a chosen code. This allows calling scripts or users to diagnose failures precisely. Documenting these codes in script headers or help text improves usability. For instance, a backup script might use exit 5 if the target disk is full, enabling automated recovery systems to react appropriately.
#!/bin/bash
# custom-exit.sh
FILE=$1
if [ -z "$FILE" ]; then
echo "Error: No file specified"
exit 1
fi
if [ ! -f "$FILE" ]; then
echo "Error: File not found"
exit 2
fi
echo "File exists: $FILE"
exit 0
Usage & Output:
$ ./custom-exit.sh
Error: No file specified
$ echo $?
1
🔹 Error Trapping with Trap
The trap command executes specified code when signals or script exit occurs. It's essential for cleanup—ensuring temporary files are deleted, locks released, or logs written even on unexpected termination. For example, trap "rm -f /tmp/mytemp.$$" EXIT removes a temp file when the script ends. You can trap signals like INT (Ctrl+C) or TERM to handle interruptions gracefully. This mechanism guarantees resource management and maintains system integrity, making scripts production-ready and reliable under various termination scenarios.
#!/bin/bash
# trap-demo.sh
cleanup() {
echo "Cleaning up..."
rm -f /tmp/tempfile
}
trap cleanup EXIT
trap 'echo "Error occurred!"' ERR
echo "Creating temp file..."
touch /tmp/tempfile
echo "Done"
Output:
Creating temp file...
Done
Cleaning up...
🔹 Logical Operators for Error Handling
Use && (AND) and || (OR) for compact, inline error control. The && operator runs the next command only if the previous succeeds. Conversely, || runs the next command only on failure. This creates clean one-liners: command1 && command2 sequences actions, while command1 || echo "Failed" provides fallback behavior. For example, cp file.txt /backup/ && echo "Backup succeeded" || { echo "Backup failed"; exit 1; }. It's a concise, readable pattern that reduces multiline if statements for simple conditions.
#!/bin/bash
# logical-operators.sh
# Run second command only if first succeeds
cd /tmp && echo "Changed directory"
# Run second command only if first fails
cd /invalid 2>/dev/null || echo "Failed to change directory"
# Chain multiple commands
mkdir testdir && cd testdir && touch file.txt && echo "All succeeded"
Output:
Changed directory
Failed to change directory
All succeeded
🔹 Comprehensive Error Handling
Combine set -e, trap, validation, and clear messaging for robust scripts. Start with set -euo pipefail to exit on errors, unset variables, or pipeline failures. Use trap for guaranteed cleanup of resources. Validate all inputs and critical command outputs. Provide informative error messages that log context (timestamp, failing command) for debugging. This layered approach ensures scripts fail early, leave no mess, and communicate issues clearly to users or logs. It's the hallmark of professional, maintainable, and secure shell scripting in production environments.
#!/bin/bash
# robust-script.sh
set -e # Exit on error
set -u # Exit on undefined variable
cleanup() {
echo "Performing cleanup..."
}
trap cleanup EXIT
# Validate input
if [ $# -eq 0 ]; then
echo "Usage: $0 <filename>"
exit 1
fi
FILE=$1
# Check file exists
if [ ! -f "$FILE" ]; then
echo "Error: File '$FILE' not found"
exit 2
fi
echo "Processing $FILE..."
# Process file here
echo "Success!"
Usage & Output:
$ ./robust-script.sh data.txt
Processing data.txt...
Success!
Performing cleanup...