Scripts take arguments so they can be reused: backup.sh daily versus four near-identical copies of the same file. Bash gives you two layers — positional parameters for simple cases, getopts for real flag parsing — and a usage guard for everything in between.
Positional parameters #
$1, $2, … are the arguments in order; $# is the count, "$@" is all of them safely quoted, and $0 is the script's own name — the raw material of any usage message:
#!/usr/bin/env bash
if [[ $# -ne 2 ]]; then
echo "usage: $0 <source> <destination>" >&2
exit 1
fi
src="$1" dst="$2"
echo "copying $src to $dst"
shift drops the first argument and slides the rest down — the standard move after consuming $1, as in the functions note.
Flags with getopts #
The moment users expect -v, -o file, getopts is the built-in parser:
#!/usr/bin/env bash
verbose=0
output="out.txt"
while getopts ":vo:" opt; do
case "$opt" in
v) verbose=1 ;;
o) output="$OPTARG" ;;
\?) echo "unknown flag: -$OPTARG" >&2; exit 1 ;;
:) echo "-$OPTARG needs a value" >&2; exit 1 ;;
esac
done
shift $((OPTIND - 1))
echo "verbose=$verbose output=$output files: $*"
The leading : in the spec turns on quiet, manual error handling; \? catches unknown flags and : catches missing values. After the loop, shift $((OPTIND - 1)) leaves only the non-flag arguments — which a case or loop then processes.
| Style | Good for | Example |
|---|---|---|
| Positional | 1–3 fixed arguments | deploy.sh prod app.conf |
| getopts | Short flags, some with values | backup.sh -v -o out.tar src |
| Long options | Full CLI feel | --output file (hand-rolled or getopt -s) |
Defaults and environment overrides #
Arguments pair naturally with parameter expansion defaults — flag beats argument beats environment beats constant:
region="${1:-${AWS_REGION:-eu-west-1}}"
Flags change behaviour; behaviour changes need strict mode so mistakes stop the script. The scripting series continues.
