Skip to content
Lesson 11 of 14

Step 1 of 5 · Reading · ~3 min

Learn

Arrays 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 how many distinct words the input line holds -- duplicates count once. It earns its place in the hygiene lesson because the right answer is a pipeline that is correct by construction: no loop, no counter, nothing left unquoted. The Unix way of not having bugs is having no code.

Three stages, and the lesson has already shown you every one of them somewhere: get one word per line, reduce to the unique set, count what is left. tr, sort and wc each do exactly one of those, and man will confirm which flag you want faster than guessing does.

The starter ships with the strict-mode preamble, and there is a subtlety worth noticing: the line is already in a variable, so a pipeline written as if it could read stdin gets nothing (the read consumed it), and under set -u a bare "$line" at the head of a pipeline is not a command -- bash tries to execute the text it holds. Both mistakes are in this exercise's submission history. Feed the variable into the pipeline deliberately.

Up nextCommand SubstitutionCommand Substitution and Exit Status

Discussion

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

Sign in to post a comment or reply.

Loading…