Skip to content

Exit_status

Chapter 30: Exit Status and Error Handling

Section titled “Chapter 30: Exit Status and Error Handling”

Exit status and error handling are critical for writing robust bash scripts. Proper error handling ensures your scripts behave predictably and provide useful feedback when things go wrong - essential for DevOps automation.


Every command in Linux returns an exit status (0-255):

  • 0 = Success
  • Non-zero = Failure (1-255)
┌────────────────────────────────────────────────────────────────┐
│ Exit Status Flow │
├────────────────────────────────────────────────────────────────┤
│ │
│ Command runs │
│ │ │
│ ▼ │
│ ┌─────────┐ │
│ │ Returns │ │
│ │ Exit │ │
│ │ Code │ │
│ └────┬────┘ │
│ │ │
│ ▼ │
│ Stored in $? │
│ │
│ ┌──────────────────────────────────────┐ │
│ │ 0 = Success │ │
│ │ 1 = General error │ │
│ │ 2 = Misuse of shell command │ │
│ │ 126 = Command not executable │ │
│ │ 127 = Command not found │ │
│ │ 128+N = Signal N termination │ │
│ └──────────────────────────────────────┘ │
│ │
└────────────────────────────────────────────────────────────────┘

Terminal window
# Basic check
ls /etc/passwd
echo $? # 0 (success)
ls /nonexistent
echo $? # 2 (failure)
# Capture exit status
command
status=$?
if [[ $status -ne 0 ]]; then
echo "Command failed with status $status"
fi
Terminal window
# Using if
if ls /etc/passwd; then
echo "File exists"
fi
# Using ||
if ! ls /etc/passwd; then
echo "File does not exist"
fi
# Using &&
ls /etc/passwd && echo "File exists"

CodeMeaning
0Success
1General error
2Misuse of shell command
126Command not executable
127Command not found
128Invalid exit argument
130Script terminated (Ctrl+C)
255Exit out of range
CodeSignal
129SIGHUP (1)
130SIGINT (2)
131SIGQUIT (3)
137SIGKILL (9)
143SIGTERM (15)

#!/bin/bash
# Simple error check
command || { echo "Command failed"; exit 1; }
# With more detail
if ! command; then
echo "Command failed"
exit 1
fi
#!/bin/bash
# Exit on error
set -e
# Commands that fail will cause script to exit
command_that_might_fail
# Continue if error is expected
command_that_might_fail || true
#!/bin/bash
# Exit on error and unset variable
set -eu
# Or more comprehensive
set -o errexit -o nounset -o pipefail
# Function for error handling
error() {
echo "ERROR: $*" >&2
exit 1
}
# Use error function
command || error "command failed"

#!/bin/bash
# Function with exit status
check_file() {
if [[ -f "$1" ]]; then
echo "File exists"
return 0
else
echo "File not found"
return 1
fi
}
# Using function
check_file "/etc/passwd" && echo "Found" || echo "Not found"
#!/bin/bash
# Exit with specific status
if [[ $# -lt 1 ]]; then
echo "Usage: $0 <argument>"
exit 1
fi
# Exit with 0 for success
exit 0
# Exit with 1 for error
exit 1

#!/bin/bash
# Database backup with error handling
set -o pipefail
DB_NAME="${DB_NAME:-appdb}"
BACKUP_DIR="${BACKUP_DIR:-/backups}"
log() { echo "[$(date)] $*"; }
error() {
log "ERROR: $*" >&2
exit 1
}
# Check prerequisites
command -v mysqldump >/dev/null 2>&1 || error "mysqldump not found"
# Create backup
timestamp=$(date +%Y%m%d_%H%M%S)
backup_file="$BACKUP_DIR/${DB_NAME}_${timestamp}.sql.gz"
log "Starting backup of $DB_NAME"
if ! mysqldump -u root "$DB_NAME" | gzip > "$backup_file"; then
error "mysqldump failed"
fi
if [[ ! -s "$backup_file" ]]; then
error "Backup file is empty"
fi
log "Backup completed: $backup_file"
exit 0
#!/bin/bash
# Health check script with retry
set -euo pipefail
URL="${1:-http://localhost:8080/health}"
MAX_RETRIES="${2:-3}"
RETRY_DELAY="${3:-5}"
check_health() {
curl -sf -o /dev/null -w "%{http_code}" "$URL" | grep -q "200"
}
for attempt in $(seq 1 $MAX_RETRIES); do
if check_health; then
echo "Service is healthy"
exit 0
fi
echo "Attempt $attempt/$MAX_RETRIES failed, retrying in ${RETRY_DELAY}s..."
sleep $RETRY_DELAY
done
echo "Service is unhealthy after $MAX_RETRIES attempts"
exit 1
#!/bin/bash
# Deployment script with rollback
set -euo pipefail
APP_DIR="/opt/myapp"
BACKUP_DIR="/opt/backup"
log() { echo "[$(date)] $*"; }
# Create backup
log "Creating backup..."
tar -czf "$BACKUP_DIR/app_$(date +%Y%m%d_%H%M%S).tar.gz" -C "$(dirname $APP_DIR)" "$(basename $APP_DIR)" || {
log "Backup failed, aborting deployment"
exit 1
}
# Deploy new version
log "Deploying new version..."
if ! rsync -av --delete ./app/ "$APP_DIR/"; then
log "Deployment failed"
log "Restoring from backup..."
# Restore would go here
exit 1
fi
# Restart service
log "Restarting service..."
if ! systemctl restart myapp; then
log "Service restart failed"
exit 1
fi
log "Deployment completed successfully"
exit 0

Terminal window
# Without pipefail
set -e
false | true # Succeeds (last command is true)
# With pipefail
set -o pipefail
false | true # Fails (first command failed)
Terminal window
# Check specific command in pipeline
command1 | command2 | command3
status=${PIPESTATUS[0]} # Status of command1
echo "${PIPESTATUS[@]}" # Status of all commands

#!/bin/bash
cleanup() {
local exit_status=$?
echo "Cleaning up..."
# Cleanup code here
echo "Exit status: $exit_status"
exit $exit_status
}
trap cleanup EXIT
trap 'echo "Interrupted"; exit 130' INT TERM

Terminal window
# && - Run if previous succeeded
command1 && command2 && command3
# || - Run if previous failed
command1 || fallback_command
# ; - Always run
command1 ; command2
Terminal window
# Run multiple commands
command1 && {
command2
command3
} || echo "Commands failed"
# Complex condition
if command1 && command2 || command3; then
echo "Success"
fi

In this chapter, you learned:

  • ✅ What exit status is and how it works
  • ✅ Checking exit status with $?
  • ✅ Common exit codes and their meanings
  • ✅ Basic and advanced error handling patterns
  • ✅ Returning exit status from functions
  • ✅ Real-world DevOps examples
  • ✅ Pipeline exit status and pipefail
  • ✅ Using trap for cleanup
  • ✅ Testing exit status with conditions

Continue to the next chapter to learn about Debugging Techniques.


Previous Chapter: Command Substitution Next Chapter: Debugging Techniques