<aside> ✂️
Module 9 — Parameter Expansion and String Manipulation. Trimming extensions, default values, extracting pieces of paths and versions — pure-bash string surgery that replaces a dozen external commands.
🧠 concept → 🧩 analogy → 🧪 exercise → ✅ expected result (hidden) → 🎯 interview questions (hidden)
</aside>
<aside> 📋
Before you start. You need Modules 1–8 — especially ${var} braces and quoting (Module 2), glob patterns (Modules 2 and 5 — the trim operators reuse them), and functions (Module 8 — this module's tools live inside helpers). Two earlier previews get their full story here: ${var:-default} (Module 7) and ${0##*/} (Module 8). Tools: nothing new.
</aside>
:- family<aside> 📖
Official docs: bash(1) — man7.org, "Parameter Expansion" section
</aside>
Everything in this module happens inside the ${ } braces you met in Module 2 — the braces are not just a name boundary; they are a tiny workshop where the value can be transformed on the way out. The first family handles bash's oldest hazard, the silent empty expansion (Module 2 A1), with four one-character policies:
| Spelling | If var is unset or empty… | Production use |
|---|---|---|
${var:-fallback} |
expand to fallback; var itself unchanged | read-time defaults: "${TIMEOUT:-30}" |
${var:=fallback} |
expand to fallback and assign it to var | set-once defaults early in a script |
${var:?message} |
print message to stderr and abort (non-zero) | required variables: : "${API_KEY:?API_KEY is required}" |
${var:+word} |
the reverse: expand to word only if var has a value | conditional flags: ${VERBOSE:+--verbose} |
The :? guard usually rides on the do-nothing command : (a colon — it ignores its arguments and succeeds), giving the one-line required-variable check above: if API_KEY is missing the script stops with your message; if present, the line does nothing at all. Dropping the colon inside the braces (${var-fallback}) changes "unset or empty" to strictly "unset" — a distinction Module 2's unset-vs-empty section planted, now with a use.
<aside> 🧩
Real-world analogy — the form with printed defaults. A well-designed order form has policies for blank fields, printed right on it: "leave blank for standard shipping" (:-), "blank = we fill in standard and it becomes your choice" (:=), "THIS FIELD IS REQUIRED — form rejected if blank" (:?), and the odd one, "check here ONLY IF the field above is filled" (:+). The clerk applies the policy without asking anyone.
Where the analogy stops working. A human clerk distinguishes "field left blank" from "field filled with whitespace" from "form has no such field." Bash's colon-forms lump unset and empty together — a variable set to "" triggers the same policy as one that never existed — and a value of a single space counts as filled. When those distinctions matter (they occasionally do in config handling), the colonless forms and manual -z checks are the precision tools.
</aside>
🧪 Exercise 9.1 — four policies, felt
The last line aborts on purpose.
unset region
echo "read-default: [${region:-eu-west-1}] — and region is still: [$region]"
echo "set-default: [${region:=eu-west-1}] — and region is now: [$region]"
count=0
echo "zero is a value: [${count:-5}]"
flag=1
echo "flag set: [${flag:+--verbose}] flag unset: [${nope:+--verbose}]"
: "${MUST_EXIST:?is required}"
echo "you will not see this"
# and %, the path surgeons<aside> 📖
Official docs: bash(1) — man7.org, "Parameter Expansion" section · basename(1) — man7.org · dirname(1) — man7.org
</aside>
Four operators remove a matching piece from a value's edge, and they reuse the glob language you already know (*, ? — Module 2 B3, Module 5's case patterns):