How to Exit Script with Error Code in Bash
Quick Answer: How to Exit with an Error Code
Use exit with a number: exit 0 for success, exit 1 (or any non-zero) for failure. Check the exit code of the last command with $?, or use if command; then to check directly in conditionals.
Quick Comparison: Error Handling Methods
| Method | Use Case | Clarity |
|---|---|---|
| $? | Check most recent exit code | Very clear |
| if command; | Chain logic based on success | Excellent |
| && and || | Inline chaining | Concise |
| Custom codes | Distinguish error types | Best for automation |
Bottom line: Use exit 0 for success, exit 1 for general errors, and exit 2+ for specific error types in automation scripts.
Understanding Exit Codes
Every command you run in Bash has an exit code—a number that tells you whether the command succeeded or failed. Exit codes are the fundamental way Bash scripts communicate results to their callers. They’re essential for writing reliable automation because they let you detect problems and respond appropriately.
Here’s the convention:
- 0 means success (the command did what you asked)
- Any non-zero value (1-255) means failure
- Different error codes can indicate different types of failures
This might feel backwards if you’re from other languages (where 0 is often false), but it’s consistent with Unix philosophy. Success returns 0 because it’s used as a truth value in conditionals, and in Unix, 0 is true.
Checking the Exit Code of the Last Command
After any command runs, Bash stores its exit code in a special variable called $?. You can check this immediately to see if the command succeeded or failed.
$ ls /home
user1 user2
$ echo $?
0
$ ls /nonexistent
ls: cannot access '/nonexistent': No such file or directory
$ echo $?
2
The ls command that succeeded returned 0. The one that failed returned 2 (a “no such file or directory” error). Each command has its own exit code—the successful ls returned 0, the failed one returned 2.
Important gotcha: The value of $? only shows the exit code of the most recent command. Once you run another command, $? changes to that command’s exit code:
$ ls /nonexistent # This fails
ls: cannot access '/nonexistent': No such file or directory
$ echo "Checking..." # This succeeds
Checking...
$ echo $?
0 # This shows the exit code of echo, not ls!
This is a common mistake—you check a command’s result, run another command (like echo), then check $? and get the wrong result. The solution is to save the exit code immediately if you need it later:
$ ls /nonexistent
ls: cannot access '/nonexistent': No such file or directory
$ error_code=$?
$ echo "The error was: $error_code"
The error was: 2
When to Use $? Exit Codes
Use $? when:
- You need to check if a command succeeded or failed
- You want to know the specific error code
- You’re debugging and need to understand why something failed
Using Exit Codes in Scripts
The most common use of exit codes is controlling script flow with if statements:
#!/bin/bash
# Check if a file exists
if [ -f "$1" ]; then
echo "File exists: $1"
else
echo "File does not exist: $1"
exit 1 # Exit with error code 1
fi
# Check if a command succeeds
if grep -q "password" /etc/shadow; then
echo "Shadow file contains password entries"
else
echo "Failed to read shadow file"
exit 2 # Exit with error code 2
fi
echo "Script completed successfully"
exit 0 # Exit with success code
Using the Exit Code of a Command Directly
You can use a command directly in an if statement - the if statement checks the exit code:
#!/bin/bash
# Using grep with exit code
if grep -q "admin" /etc/passwd; then
echo "Admin user found"
else
echo "Admin user not found"
fi
# Using a test command
if [ -d "/home" ]; then
echo "Home directory exists"
else
echo "Home directory not found"
exit 1
fi
# Using ping to check connectivity
if ping -c 1 google.com > /dev/null 2>&1; then
echo "Internet connection is available"
else
echo "No internet connection"
exit 1
fi
Practical Example: Backup Script with Error Handling
Here’s a real-world backup script that uses exit codes properly:
#!/bin/bash
BACKUP_DIR="/backups"
SOURCE_DIR="/home/user/important_files"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/backup_$TIMESTAMP.tar.gz"
# Check if source directory exists
if [ ! -d "$SOURCE_DIR" ]; then
echo "ERROR: Source directory does not exist: $SOURCE_DIR"
exit 1
fi
# Check if backup directory exists
if [ ! -d "$BACKUP_DIR" ]; then
echo "ERROR: Backup directory does not exist: $BACKUP_DIR"
exit 1
fi
# Create the backup
echo "Creating backup: $BACKUP_FILE"
tar -czf "$BACKUP_FILE" "$SOURCE_DIR"
# Check if backup was successful
if [ $? -eq 0 ]; then
echo "Backup completed successfully"
echo "File size: $(du -h "$BACKUP_FILE" | cut -f1)"
exit 0
else
echo "ERROR: Backup failed!"
exit 1
fi
When this script runs:
- It verifies the source and backup directories exist (exiting with code 1 if not)
- It creates the backup
- It checks if tar succeeded (exit code 0)
- It exits with appropriate codes so the calling process knows what happened
Chaining Commands with && and ||
You can chain commands using && (run if previous succeeded) and || (run if previous failed):
# && operator: only run if previous command succeeded
$ mkdir /tmp/test && cd /tmp/test && echo "Directory created and entered"
Directory created and entered
# || operator: only run if previous command failed
$ ls /nonexistent || echo "Directory not found, continuing..."
ls: cannot access '/nonexistent': No such file or directory
Directory not found, continuing...
# Combining both
$ mkdir /tmp/newdir && cp file.txt /tmp/newdir || echo "Copy failed"
This is much cleaner than writing if-else statements for every command.
Custom Exit Codes in Your Scripts
You can define your own exit codes to indicate different types of errors:
#!/bin/bash
# Define exit codes at the top for clarity
readonly E_SUCCESS=0
readonly E_MISSING_FILE=1
readonly E_INVALID_FORMAT=2
readonly E_WRITE_FAILED=3
if [ ! -f "$1" ]; then
echo "ERROR: File not found: $1"
exit $E_MISSING_FILE
fi
if ! grep -q "^valid_header" "$1"; then
echo "ERROR: File does not have valid header"
exit $E_INVALID_FORMAT
fi
if ! cp "$1" "$1.backup"; then
echo "ERROR: Failed to create backup"
exit $E_WRITE_FAILED
fi
echo "File processed successfully"
exit $E_SUCCESS
Now callers can check the specific error code to understand what went wrong:
$ ./process.sh myfile.txt
File processed successfully
$ echo $?
0
Printing Exit Code on Test Failures
When debugging, it’s helpful to see the actual exit code:
#!/bin/bash
# Test a command and show its exit code
test_command() {
local cmd="$1"
echo "Running: $cmd"
eval "$cmd"
local exit_code=$?
echo "Exit code: $exit_code"
return $exit_code
}
test_command "ls /home"
test_command "ls /nonexistent"
test_command "grep root /etc/passwd"
Output:
Running: ls /home
user1 user2
Exit code: 0
Running: ls /nonexistent
ls: cannot access '/nonexistent': No such file or directory
Exit code: 2
Running: grep root /etc/passwd
root:x:0:0:root:/root:/bin/bash
Exit code: 0
Key Points to Remember
- Exit code 0 means success, any non-zero means failure
- Check the exit code with
$?immediately after a command - Use
if command; thento check exit codes in conditionals - Use
&&and||to chain commands based on exit codes - Define your own exit codes for better error handling
- Exit with appropriate codes so parent processes know what happened
- Don’t assume a command succeeded - always check the exit code
Common Exit Codes
| Code | Meaning |
|---|---|
| 0 | Success |
| 1 | General errors |
| 2 | Misuse of shell command |
| 126 | Command cannot execute |
| 127 | Command not found |
| 128 | Invalid exit argument |
| 255 | Exit status out of range |
Summary
Exit codes are how Bash scripts communicate success or failure. By using them properly, you can write scripts that handle errors gracefully, chain commands logically, and provide useful feedback to whoever runs your script. Always check exit codes and use them to control your script’s flow.