Skip to content
Script Hygiene
step 1/5

Reading — step 1 of 5

Learn

~3 min readArrays and Scripts

Script Hygiene

Every bash lesson so far taught you a way scripts work. This one teaches the ways they fail silently — and the four-line preamble professionals paste at the top of every script to make failures loud, early, and safe. This is the difference between bash you run and bash you trust.

The unofficial strict mode

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

Three flags, each closing a real hole:

  • set -e — exit on any command failure. Default bash keeps going after errors: cd /nope; rm -rf * fails the cd… then runs the rm in the wrong directory. That's not a hypothetical; it's the shape of several famous disasters. With -e, the script dies at the cd.
  • set -u — unset variables are errors. Default bash expands typos to empty: rm -rf "$TMP_DRI/" (note the typo) becomes rm -rf "/" .With -u: TMP_DRI: unbound variable, exit, crisis averted.
  • set -o pipefail — a pipeline fails if ANY stage fails. Default: only the last stage's status counts, so grep pattern missing-file | sort succeeds (sort worked!) while grep's error scrolls past. With pipefail, the pipeline reports the failure.

Honest footnote: set -e has edge cases (commands in if conditions are exempt — deliberately — and a few others), and shell veterans argue about it. The pragmatic consensus this course teaches: use strict mode; learn the exemptions as you meet them. A script that dies too eagerly is a bug report; a script that plows on is an incident.

Quote everything (the recap that's a rule)

You've heard it every lesson; here it's policy: "$var", "${arr[@]}", "$@" — quoted, always. Unquoted expansion + word-splitting + globbing is bash's largest bug class, and quoting is its complete cure. Related armor: mkdir -p (no error if it exists), rm -f (no error if it doesn't), and -- to end option parsing (rm -- "$file" survives filenames starting with -).

shellcheck: the reviewer that never sleeps

$ shellcheck script.sh

ShellCheck is a static analyzer that knows every trap in this course and hundreds more — unquoted expansions, useless cats, wrong test operators, the works — each with a wiki page explaining why. It runs in editors, in CI, and at shellcheck.net. Professional bash and shellcheck-clean bash are, at this point, the same thing. Adopt it today; it's the closest thing bash has to Rust's compiler.

Fail with useful messages

die() {
    echo "ERROR: $*" >&2      # errors go to STDERR — never pollute stdout
    exit 1
}

[[ -f "$input" ]] || die "input file not found: $input"

The die helper appears in some form in every serious script: message to stderr (pipes lesson — results and errors are different streams), non-zero exit so callers and set -e notice. $* joins all arguments into the message.

Your exercise: Distinct Words

Count the distinct words in the input — a one-pipeline job that earns its place in the hygiene lesson because the pipeline is correct by construction:

#!/usr/bin/env bash
set -euo pipefail
tr -s ' \t' '\n' | sort -u | wc -l

Read it as a sentence: squeeze whitespace into newlines (one word per line), sort -u for the unique set, count lines. No loops, no variables, nothing to leave unquoted — the Unix way of not having bugs is having no code. Ship it with the preamble anyway: the habit is the lesson, and the next script won't be three stages long.

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…