Linux Basic — Handling Errors Practice Exercises
Handling Errors — Practice Exercises
Five exercises spanning exit codes, strict mode, traps, and helper functions.
Exercise 1 — Build a die That Accepts an Exit Code
Goal: Write a die function that accepts an optional numeric first arg as the exit code (defaulting to 1) and prints all remaining args as the message to stderr. Then exits.
Demonstrate with:
die "generic failure"
die 2 "bad arguments"
die 42 "magic answer"
💡 Show Answer
die() {
local rc=1
if [[ "${1:-}" =~ ^[0-9]+$ ]]; then
rc=$1
shift
fi
printf 'ERROR: %s\n' "$*" >&2
exit "$rc"
}
# Demo (each in a subshell so script doesn't actually exit)
(die "generic failure")
echo "rc=$?"
# ERROR: generic failure
# rc=1
(die 2 "bad arguments")
echo "rc=$?"
# ERROR: bad arguments
# rc=2
(die 42 "magic answer")
echo "rc=$?"
# ERROR: magic answer
# rc=42
Key points:
${1:-}soset -udoesn’t bite when called with no args=~ ^[0-9]+$correctly distinguishes a numeric first arg from a message starting with a numberprintf '%s\n'instead ofechois safer for arbitrary content
Exercise 2 — Self-Cleaning Temp Directory
Goal: Write a script that creates a temp directory, writes a few files in it, then exits. The temp directory must be deleted on every exit path: normal exit, die exit, Ctrl-C, and an unhandled set -e error. Prove it by running the script and checking that $tmpdir no longer exists afterward.
💡 Show Answer
#!/usr/bin/env bash
set -euo pipefail
tmpdir=$(mktemp -d)
echo "Created: $tmpdir"
cleanup() {
local rc=$?
rm -rf -- "$tmpdir"
echo "Cleaned up (exit $rc)"
}
trap cleanup EXIT
trap 'echo "Interrupted"; exit 130' INT
# Create some files
touch "$tmpdir/a.txt" "$tmpdir/b.txt"
ls "$tmpdir"
# Uncomment ONE to test different exit paths:
# exit 0 # normal exit
# false # set -e abort
# kill -INT $$ # SIGINT to self
# echo "$undef" # set -u abort
echo "Normal end"
Test all four paths:
$ ./script.sh
Created: /tmp/tmp.XXX
a.txt b.txt
Normal end
Cleaned up (exit 0)
$ ls /tmp/tmp.XXX
ls: cannot access ...: No such file or directory ✓
Key insights:
trap cleanup EXITcovers normal exit,die,set -eabort, and signals (because signals trigger EXIT)- The explicit
trap … INTis optional but lets you customize the SIGINT message and exit code - Capture
$?as the very first line of the cleanup function
Exercise 3 — Detect Pipeline Failure
Goal: Write a function fetch_count that runs curl -sS --fail "$url" | wc -l and returns the line count. It must:
- Fail (return non-zero) if
curlfails (e.g., 404) - Fail if
wcfails - Print the count on success
Demonstrate with a real URL and a bogus URL.
💡 Show Answer
fetch_count() {
local url=$1
local count rc
# Local pipefail just for this function
set -o pipefail
count=$(curl -sS --fail "$url" | wc -l)
rc=$?
if (( rc != 0 )); then
echo "fetch failed for $url (exit $rc)" >&2
echo "PIPESTATUS = ${PIPESTATUS[*]}" >&2
return "$rc"
fi
echo "$count"
}
# Good URL
fetch_count https://example.com
# 46
# Bad URL
fetch_count https://example.com/nope-404
# fetch failed for https://example.com/nope-404 (exit 22)
# PIPESTATUS = 22 0
Without pipefail, the bad URL case would print 0 (because wc -l succeeds with empty input) and the function would return success — silently wrong.
PIPESTATUS shows exactly which stage failed: 22 0 means curl (stage 0) returned 22 and wc (stage 1) returned 0.
Exercise 4 — require Dependencies Up Front
Goal: Write require_cmd (binary tools) and require_var (env vars) helpers. Use them at the top of a script that needs jq, curl, awk and the env vars API_TOKEN and REGION. The script must list all missing items in one go before exiting — don’t fail on the first one.
💡 Show Answer
#!/usr/bin/env bash
set -euo pipefail
die() { printf 'ERROR: %s\n' "$*" >&2; exit 1; }
require_cmd() {
local cmd missing=()
for cmd in "$@"; do
command -v "$cmd" >/dev/null 2>&1 || missing+=("$cmd")
done
if ((${#missing[@]})); then
die "missing command(s): ${missing[*]}"
fi
}
require_var() {
local v missing=()
for v in "$@"; do
[[ -n "${!v:-}" ]] || missing+=("$v")
done
if ((${#missing[@]})); then
die "missing env var(s): ${missing[*]}"
fi
}
require_cmd jq curl awk
require_var API_TOKEN REGION
echo "All deps present, proceeding..."
Test with nothing set:
$ ./script.sh
ERROR: missing env var(s): API_TOKEN REGION
$ echo $?
1
The key idea: collect first, report once. Don’t die on the first miss — users want to know everything they’re missing in one go.
${!v:-} is indirect expansion with default-empty — safe under set -u.
Exercise 5 — Graceful Long-Running Loop
Goal: Write a script that runs an infinite loop printing the time once per second. On SIGINT (Ctrl-C) or SIGTERM, it should:
- Print “Caught signal — shutting down”
- Print a summary: “Ran for N seconds”
- Exit with code 130 (SIGINT) or 143 (SIGTERM) appropriately
💡 Show Answer
#!/usr/bin/env bash
set -euo pipefail
start=$SECONDS
caught=""
shutdown() {
local sig=$1
caught=$sig
local rc
case "$sig" in
INT) rc=130 ;;
TERM) rc=143 ;;
*) rc=1 ;;
esac
echo
echo "Caught $sig — shutting down"
echo "Ran for $(( SECONDS - start )) seconds"
exit "$rc"
}
trap 'shutdown INT' INT
trap 'shutdown TERM' TERM
while true; do
printf '%(%H:%M:%S)T\n' -1
sleep 1
done
Test:
$ ./loop.sh
09:00:01
09:00:02
09:00:03
^C
Caught INT — shutting down
Ran for 3 seconds
$ echo $?
130
Notes:
$SECONDSis a Bash built-in counter — seconds since shell startprintf '%(%H:%M:%S)T\n' -1is the built-in time formatter (Bash 4.2+) — nodatefork- 130 = 128 + 2 (SIGINT), 143 = 128 + 15 (SIGTERM) — standard “killed by signal” convention
- The trap must call exit explicitly — otherwise the loop continues after the handler returns
For a real worker (with cleanup):
trap 'echo "shutting down"; rm -f "$lockfile"; exit 130' INT TERM
while true; do
work_one_unit || true
sleep 1
done
What’s Next?
You can now write Bash scripts that fail predictably, clean up after themselves, and tell users exactly what went wrong. The next chapter is the other half of the equation — debugging scripts that have a bug despite all this defensive coding: set -x, bash -x, ShellCheck, debug modes, and the catalog of “classic” shell bugs.
Continue → Debugging Techniques