Skip to content

Latest commit

 

History

History
207 lines (154 loc) · 5.15 KB

File metadata and controls

207 lines (154 loc) · 5.15 KB

03. Debugging and Error Handling

Status Module Bash

🐛 Why Error Handling Matters

By default, Bash will happily keep running even if a command fails. This can lead to cascading failures that are difficult to diagnose:

#!/bin/bash
# Without error handling — DANGEROUS
cd /nonexistent_dir    # Fails silently
rm -rf *               # Now deletes files from the WRONG directory!

Professional scripts fail loudly and early, making debugging easy.


🔒 The Safety Header — set Options

Place this near the top of every production script:

#!/usr/bin/env bash
set -euo pipefail

This single line activates three critical safety options:

Option Effect
set -e Exit immediately if any command fails (non-zero exit code)
set -u Exit with error if an undefined variable is used
set -o pipefail Pipeline fails if any command in it fails (not just the last)
#!/usr/bin/env bash
set -euo pipefail

# -e catches this: script exits instead of continuing
cp important.db /backup/ || { echo "Copy failed!"; exit 1; }

# -u catches this: using $UNDEFINED_VAR would exit with an error
echo "$UNSET_VARIABLE"    # Error: UNSET_VARIABLE: unbound variable

# -o pipefail catches this:
grep "pattern" nonexistent_file | wc -l   # Without pipefail, exits 0

💡 Add IFS=$'\n\t' alongside set -euo pipefail to prevent word-splitting bugs on whitespace.


🔍 Trace Mode — set -x

set -x prints each command to stderr before executing it, prefixed with +. This is your most powerful debugging tool.

#!/usr/bin/env bash
set -x    # Enable trace

name="World"
echo "Hello, $name"
# Output:
# + name=World
# + echo 'Hello, World'
# Hello, World

set +x    # Disable trace (useful to reduce noise in loops)

Debug only a specific section:

#!/usr/bin/env bash
set -euo pipefail

prepare_data
transform_data

set -x    # Start tracing only from here
load_data
set +x    # Stop tracing

generate_report

Run a script in debug mode without modifying it:

bash -x ./myscript.sh
bash -euxo pipefail ./myscript.sh

📤 Exit Codes

Every command in Linux exits with a code from 0 to 255:

  • 0 = success
  • 1 = general error
  • 2 = misuse of shell builtin
  • 126 = command found but not executable
  • 127 = command not found
  • 130 = script terminated by Ctrl+C
ls /etc/hosts
echo "Exit code: $?"    # 0

ls /nonexistent
echo "Exit code: $?"    # 2

# Always exit with meaningful codes from your own scripts
validate_input() {
    [[ -z "$1" ]] && return 1
    [[ "$1" =~ ^[0-9]+$ ]] || return 2
    return 0
}

validate_input ""       && echo "ok" || echo "Empty input (code: $?)"
validate_input "abc"    && echo "ok" || echo "Not a number (code: $?)"
validate_input "42"     && echo "ok" || echo "err"

🪤 trap ERR — Catching Errors Automatically

Combine set -e with trap ERR to get informative error messages:

#!/usr/bin/env bash
set -euo pipefail

# Error handler
err_handler() {
    local exit_code="$?"
    local line_number="$1"
    echo ""
    echo "❌ ERROR: Script failed at line $line_number with exit code $exit_code"
    echo "   Command: $BASH_COMMAND"
    echo "   Stack: ${FUNCNAME[*]}"
    echo ""
}
trap 'err_handler $LINENO' ERR

# --- Script body ---
echo "Starting..."
cp /nonexistent/file /tmp/   # This will trigger the error handler
echo "This line won't run."

📝 Structured Logging

Replace bare echo calls with a proper logging system:

#!/usr/bin/env bash
set -euo pipefail

readonly LOG_FILE="/var/log/myscript.log"
readonly SCRIPT_NAME="$(basename "$0")"

log() {
    local level="$1"
    shift
    local message="$*"
    local timestamp
    timestamp="$(date '+%Y-%m-%d %H:%M:%S')"
    printf "[%s] [%-7s] [%s] %s\n" "$timestamp" "$level" "$SCRIPT_NAME" "$message" | tee -a "$LOG_FILE"
}

log_info()    { log "INFO"    "$@"; }
log_warning() { log "WARNING" "$@"; }
log_error()   { log "ERROR"   "$@" >&2; }
log_debug()   { [[ "${DEBUG:-}" == "1" ]] && log "DEBUG" "$@" || true; }

# Usage
log_info "Starting backup process..."
log_warning "Disk usage is above 80%"
log_error "Database connection failed"
DEBUG=1 log_debug "Checking variable: value=$some_var"

🏆 Summary

Technique Purpose
set -euo pipefail Fail fast: exit on error, undefined vars, pipe failures
set -x Trace mode: print each command before execution
bash -x script.sh Debug without modifying the script
trap 'handler' ERR Catch errors with context (line number, command)
trap 'cleanup' EXIT Always clean up on exit
Structured log_* functions Consistent, timestamped, leveled log messages
exit N Exit with a meaningful code

👉 Next: Arrays and String Operations