Robust scripts don't just work when everything is fine — they fail gracefully and loudly when something goes wrong. In this lab you'll use set -e, set -u, set -o pipefail to make scripts fail fast, trap to run cleanup on exit or error, build custom error functions, understand exit codes, and chain commands with && and ||.
Step 1: set -e — Exit on Error
By default, Bash continues executing even after a command fails. set -e changes that:
set-eecho"Before error"false# returns exit code 1echo"This should not print"
📸 Verified Output:
Before error
(Script exits immediately after false; the last line never runs.)
💡 Tip:set -e can be counterintuitive — it also triggers on [ false ] and other exit-1 commands in conditions. Use command || true to explicitly allow a command to fail without killing the script.
Step 2: set -u — Treat Unset Variables as Errors
Accessing an unset variable silently returns empty string by default — a common source of bugs:
📸 Verified Output:
(Script exits on the unset variable access.)
💡 Tip: Use ${var:-default} to provide a fallback when a variable might be unset without triggering set -u. Example: ${CONFIG_FILE:-/etc/default.conf}.
Step 3: set -o pipefail — Catch Pipeline Failures
Without pipefail, a pipeline's exit status is only the last command's status — earlier failures are silently swallowed:
📸 Verified Output:
💡 Tip: The golden combination for reliable scripts is to put set -euo pipefail at the very top of every script. These three options together catch the most common silent failures.
Step 4: trap — Cleanup on EXIT
trap registers a handler that runs automatically when the script exits, regardless of whether it exited cleanly or due to an error:
📸 Verified Output:
💡 Tip: Use trap to remove temporary files, release locks, or kill background processes. The EXIT trap fires on both successful exit and error — which is exactly what you want for cleanup.
Step 5: trap ERR — Catch Errors with Line Numbers
The ERR trap fires whenever a command returns a non-zero exit code:
📸 Verified Output:
💡 Tip: With set -e enabled, the ERR trap fires and then the script exits. Without set -e, execution continues after the ERR trap. Combine both for maximum reliability: the trap logs context, then set -e stops execution.
Step 6: Custom Error Functions and Exit Codes
A die() function gives you a consistent, readable way to fail with a message and specific exit code:
📸 Verified Output:
(Script exits with code 2.)
💡 Tip: Always write error messages to stderr (>&2), not stdout. This lets callers capture only the useful output with result=$(script.sh) while error messages still appear on the terminal.
Step 7: && and || Chains
&& runs the next command only on success; || runs it only on failure — a compact alternative to if/else for simple cases:
📸 Verified Output:
💡 Tip: Chain multiple operations: cmd1 && cmd2 && cmd3 || handle_failure. The entire chain short-circuits on first failure and runs handle_failure. For complex logic, prefer if blocks — chains become unreadable beyond 2–3 commands.
Step 8: Capstone — Production-Grade Script Header
Combine all error handling techniques into a template used by real production scripts:
📸 Verified Output:
💡 Tip: This header is a template — save it as script_template.sh and copy it as the foundation of every new script. The combination of set -euo pipefail + trap cleanup EXIT + trap die ERR catches virtually all common failure modes automatically.