Lesson 2 | Accessing command-line arguments |
Objective | Read, validate, and iterate over command-line arguments in modern Bash scripts using positional parameters and idiomatic parsing. |
Many UNIX utilities change behavior based on parameters (for example, ls -a
or ls -l
). Your shell scripts can do the same by reading command-line arguments—values supplied after the script name. Bash exposes these as positional parameters: $0
(script/command name), $1
, $2
, …, ${10}
, and so on.
# convert <options> <files...>
echo "Script: $0"
echo "First arg: $1"
echo "Count: $#"
echo "All (split into words by IFS): $*"
echo "All (preserve each arg): $@"
Tip: Always quote expansions in loops; prefer "$@"
to preserve each argument exactly (including spaces).
Suppose you run the command below; the figures (kept from the legacy lesson) illustrate how Bash fills each parameter:
% convert -all tickets.dat agents.dat clients.dat
convert -all tickets.dat agents.dat clients.dat
$0 = convert
$1 = -all
convert -all tickets.dat agents.dat clients.dat
$0=convert
$1=-all
Why it matters: $1
is the first argument after the command name.
$2 = tickets.dat
convert -all tickets.dat agents.dat clients.dat
$0=convert
$1=-all
$2=tickets.dat
$3 = agents.dat
$4 = clients.dat
$# = 4
(number of arguments, excluding $0
)$*
and $@
:
convert -all tickets.dat agents.dat clients.dat
$0=convert
$1=-all
$2=tickets.dat
$3=agents.dat
$4=clients.dat
$#=4
$*=convert -all tickets.dat agents.dat clients.dat
$*
→ all args as one word when quoted ("$*"
).$@
→ each arg as its own word when quoted ("$@"
) — best for loops.# Process each file argument exactly as passed (handles spaces)
for f in "$@"; do
printf 'Converting: %s\n' "$f"
done
echo "Tenth argument is: ${10}"
Use braces for two-digit indexes (e.g., ${11}
).
# Example: handle an optional -a/--all flag, then files
all=false
while [ $# -gt 0 ]; do
case "$1" in
-a|--all) all=true; shift ;;
--) shift; break ;; # end of options
-*) echo "Unknown option: $1" >&2; exit 2 ;;
*) break ;; # first non-option
esac
done
# Remaining "$@" are the files
"$all" && echo "Converting all formats"
for f in "$@"; do
printf 'Convert: %s\n' "$f"
done
getopts
for short options# Supports: -a (all), -o <outdir>
all=false; outdir="."
while getopts ":ao:" opt; do
case "$opt" in
a) all=true ;;
o) outdir=$OPTARG ;;
\?) echo "Invalid option: -$OPTARG" >&2; exit 2 ;;
:) echo "Option -$OPTARG requires an argument" >&2; exit 2 ;;
esac
done
shift $((OPTIND - 1)) # drop parsed options
for f in "$@"; do
printf 'Convert: %s → %s\n' "$f" "$outdir"
done
$@
/$*
can split on spaces; use "$@"
in loops.$*
(when quoted) joins all args into one word; rarely what you want.${1:?usage message}
.*dat
) are expanded by the shell before your script runs; your script receives the expanded list.