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:

A two-argument script with a guard
#!/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:

Parse -v and -o flags, then treat the rest as files
#!/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.

StyleGood forExample
Positional1–3 fixed argumentsdeploy.sh prod app.conf
getoptsShort flags, some with valuesbackup.sh -v -o out.tar src
Long optionsFull 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:

Layered configuration
region="${1:-${AWS_REGION:-eu-west-1}}"

Flags change behaviour; behaviour changes need strict mode so mistakes stop the script. The scripting series continues.