<aside> 📥
Module 7 — Script Arguments and User Input. A script you can only edit is a note; a script that takes arguments is a tool. How deploy.sh staging --dry-run actually works.
🧠 concept → 🧩 analogy → 🧪 exercise → ✅ expected result (hidden) → 🎯 interview questions (hidden)
</aside>
<aside> 📋
Before you start. You need Modules 1–6. Load-bearing: quoting (Module 2 — the "$@" story is a quoting story), exit codes and the usage-error convention (Module 3), >&2 (Module 4), case and [ ] (Module 5), while and arithmetic (Module 6 — getopts is a while-case machine). Module 5's route.sh already used $1 on credit; this module pays that debt. Tools: nothing new.
</aside>
$1, $2, … $0, and $#<aside> 📖
Official docs: Bash manual — Special Parameters
</aside>
When someone runs ./greet.sh staging blue, bash takes the words after the script's name and files them into numbered variables before line one executes: $1 is staging, $2 is blue — the positional parameters. Three relatives complete the set: $0 holds the name the script was invoked by (./greet.sh here — useful in usage messages); $# holds the count of arguments (2 here); and positions past nine need braces — ${10} — because $10 reads as $1 followed by a literal 0 (Module 2's greedy-name rule biting in a new place).
These are variables with the usual rules — which means the usual reflex: "$1" in quotes, everywhere — an argument can contain spaces (a filename, a message) and unquoted it word-splits exactly like any other expansion. And an argument never supplied expands to nothing, silently (Module 2 A1's silence — now arriving from the outside world, where you control it even less).
<aside> 🧩
Real-world analogy — the job ticket's numbered fields. A print-shop job ticket has numbered blanks: field 1 — paper size, field 2 — copies. The customer fills the blanks; the machine operator (your script) reads them by number and never asks who wrote them. The shop's name printed on the ticket header is $0; the "fields filled: N" tally is $#.
Where the analogy stops working. A paper form shows its empty fields — you can see blank where "copies" should be. A script cannot see absence: $2 unset and $2 never-provided look identical (empty), and the machine runs anyway with a blank where a value belonged. That is why Part A2's counting guard exists — the tally $# is the only witness that fields were left unfilled.
</aside>
🧪 Exercise 7.1 — the numbered blanks
The second run is incomplete on purpose.
cd ~/bash-course
nano greet.sh # four lines:
#!/bin/bash
echo "script name: $0"
echo "first arg: $1"
echo "second arg: $2"
echo "arg count: $#"
chmod +x greet.sh
./greet.sh staging blue
./greet.sh # ← no arguments at all
Module 3 established the convention (usage error → message on stderr, exit 2); Module 5 built guards; here they fuse into the first line of virtually every production script:
[ "$#" -ge 1 ] || { echo "usage: $0 command [targets...]" >&2; exit 2; }
Read it with your accumulated toolkit: $# counted, [ -ge ] compared numerically (Module 5 B2), || fired on failure (Module 3), the message named the script via $0 and left on channel 2 (Module 4), and exit 2 told the caller which kind of failure. The { …; } braces group the two commands so || owns both — a small new piece of syntax doing exactly what it looks like. A script guarded this way fails in a millisecond with instructions, instead of failing halfway through real work with a mystery.