Scratchpad

2026-05-10 · 5 min read

Keeping shell scripts honest

The first line of every script I write is set -euo pipefail. Exit on the first failing command, treat undefined variables as errors, and do not let a failing command in the middle of a pipeline disappear silently. Without these three flags a script does not fail — it continues into a state you did not plan for.

The first bug I keep making anyway: unquoted variables. rm -rf $DIR works until DIR contains a space, and then it works in ways you will remember for a long time. Quote everything: rm -rf "$DIR". There is no performance penalty for being correct.

The second: temp directories without cleanup. mktemp -d plus a trap 'rm -rf "$TMP"' EXIT at the top of the script, always. Without the trap, every aborted run leaves garbage behind, and one day the garbage is half a disk.

The third: assuming a binary exists. A script that works on the laptop fails on the server because jq is not installed there. A one-line command -v jq >/dev/null || { echo "jq required"; exit 1; } turns a mysterious mid-script failure into a clear message.

Traps deserve more credit in general: cleanup on EXIT, a debug mode via set -x behind an environment variable, locking with flock so two cron runs do not collide. None of it is exotic; all of it is one line each.

And run shellcheck before every commit. It finds the first two bugs every single time, which suggests I will keep making them, which suggests shellcheck stays.