Przejdź do treści

⚠️ Extended Error Handling

Robust scripts anticipate failures and respond gracefully. This section covers advanced error handling patterns.

🧭 Exit Codes Recap

Every command returns 0 (success) or 1-255 (failure):

1
2
ls /exists    # Returns 0
ls /missing   # Returns 2

Capture with $?:

1
2
3
4
5
ls /missing
code=$?
if [ $code -ne 0 ]; then
    echo "Command failed with code $code"
fi

🧪 set -e (Exit on Error)

Automatically exit when any command fails:

1
2
3
4
5
6
#!/bin/bash
set -e

echo "Step 1"
false           # Script exits here
echo "Step 2"   # Never reached

Common pitfalls:

1
2
3
set -e
grep pattern file || true   # ✅ Prevents exit
grep pattern file           # ❌ Would exit if no match

Commands in conditionals don't trigger exit:

1
2
3
4
5
set -e
if false; then
    echo "Won't print"
fi
echo "This prints"   # ✅ Because false was in if condition

🧠 set -u (Exit on Undefined Variable)

Catch typos in variable names:

1
2
set -u
echo "$UNDEFINED_VAR"   # Exits with error

Provide defaults to avoid:

1
2
3
set -u
: "${VAR:=default}"
echo "$VAR"   # Safe

🧪 set -o pipefail

By default, pipeline exit code is the last command:

1
2
3
set +e   # Disable exit-on-error for demo
false | true   # Exit code: 0 (misleading!)
true | false   # Exit code: 1

With pipefail, exit code is the first failing command:

1
2
set -o pipefail
false | true   # Exit code: 1 (correct!)

Recommended combination:

1
set -euo pipefail

This is the "strict mode" used in production scripts.


🧠 Custom Error Handlers

Trap ERR

Run code whenever any command fails:

1
2
3
4
5
6
7
8
9
err_handler() {
    echo "Error on line $1" >&2
    echo "Last command: $BASH_COMMAND" >&2
}

trap 'err_handler $LINENO' ERR

false   # Triggers handler
ls /missing   # Triggers handler

Trap EXIT

Cleanup on any exit (success or failure):

1
2
3
4
5
6
7
8
cleanup() {
    rm -f /tmp/mytempfile
    echo "Cleaned up temporary files"
}

trap cleanup EXIT

# Even if script fails, cleanup runs

🧪 Error Codes Pattern

Define custom error codes:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
readonly E_OK=0
readonly E_MISSING_CONFIG=1
readonly E_NETWORK_FAILURE=2
readonly E_PERMISSION_DENIED=3

check_config() {
    [ -f config.yml ] || return $E_MISSING_CONFIG
}

check_config || {
    echo "Configuration error (code $?)" >&2
    exit $E_MISSING_CONFIG
}

This makes error handling explicit and maintainable.


🧠 Defensive Programming Patterns

Validate Inputs Early

1
2
3
4
5
6
7
8
9
if [ $# -lt 2 ]; then
    echo "Usage: $0 <input> <output>" >&2
    exit 1
fi

if [ ! -f "$1" ]; then
    echo "Input file not found: $1" >&2
    exit 2
fi

Fail Fast with Meaningful Messages

1
2
3
4
5
6
7
deploy() {
    : "${ENVIRONMENT:?Environment not set}"
    : "${VERSION:?Version not set}"

    echo "Deploying $VERSION to $ENVIRONMENT"
    # ... deployment logic
}

Check Command Prerequisites

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
require_command() {
    command -v "$1" >/dev/null 2>&1 || {
        echo "Required command not found: $1" >&2
        exit 1
    }
}

require_command curl
require_command jq
require_command git

🧪 Logging Errors

Centralize error reporting:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
log_error() {
    echo "[ERROR] $(date '+%Y-%m-%d %H:%M:%S') $*" >&2
}

log_warning() {
    echo "[WARN] $(date '+%Y-%m-%d %H:%M:%S') $*" >&2
}

log_info() {
    echo "[INFO] $(date '+%Y-%m-%d %H:%M:%S') $*"
}

# Usage
if ! mkdir -p "$TARGET_DIR"; then
    log_error "Failed to create directory: $TARGET_DIR"
    exit 1
fi

🧾 Summary

  • Use set -euo pipefail for strict error handling.
  • Trap ERR and EXIT for cleanup and logging.
  • Define custom error codes for clarity.
  • Validate inputs before processing.
  • Log errors with timestamps for debugging.
  • Always provide meaningful error messages.

👉 Continue to: Traps and Signals