Positional Parameters and Special Bash Parameters
A shell script becomes a command-line program when it accepts arguments predictably. Bash exposes those arguments through positional parameters and several special parameters. Their syntax is compact, but quoting determines whether the caller's data survives intact.
Learning objectives
By the end of this lesson
- Read positional parameters safely.
- Explain the most important special parameters.
- Preserve exact argument boundaries with quoted "$@".
- Validate required positional arguments.
- Build a small positional deployment interface.
1. Positional parameters are the argument vector
Arguments supplied after the script name become $1, $2, and so on. Inside a function, the same notation refers to that function call.
cat > show-args.sh <<'EOF'
#!/usr/bin/env bash
printf 'arg1=<%s>\n' "${1-}"
printf 'arg2=<%s>\n' "${2-}"
printf 'count=%d\n' "$#"
EOF
bash show-args.sh api "staging blue"2. $0 and $# describe the current invocation
$0Invocation name or script pathUseful in usage messages$#Number of positional argumentsUse for arity checks${10}Tenth positional argumentBraces are required beyond $9if (( $# < 2 )); then
printf 'usage: %s SERVICE ENVIRONMENT\n' "$0" >&2
exit 64
fi3. Quoted "$@" preserves every original argument
For forwarding or iteration, "$@" is the crucial form. It expands to one quoted shell word per original positional argument.
show() {
printf 'received=%d\n' "$#"
for arg in "$@"; do
printf 'arg=<%s>\n' "$arg"
done
}
show "api gateway" staging "*.yaml"Use target_command "$@" when forwarding the caller's argument vector. Do not rebuild it with unquoted expansions.
4. "$*" joins arguments into one string
Quoted "$*" joins all positional parameters using the first character of IFS. That is useful occasionally for display, but it is not a substitute for "$@" when argument boundaries matter.
set -- "api gateway" staging "*.yaml"
joined="$*"
printf 'joined=<%s>\n' "$joined"5. $? is the most recent foreground pipeline status
grep -q '^root:' /etc/passwd
status=$?
printf 'grep status=%d\n' "$status"Capture the value immediately if it matters; any later foreground command can replace it.
6. $$ and $! expose process identifiers
$$Shell process identifierUseful for diagnostics; BASHPID is also available in Bash$!PID of most recent background commandUse with waitsleep 1 &
pid=$!
printf 'shell=%s background=%s\n' "$$" "$pid"
wait "$pid"
printf 'background status=%d\n' "$?"7. Other special parameters are contextual tools
$- exposes active shell option letters, while $_ has context-dependent behavior related to previous commands. These can be useful diagnostically, but application logic is usually clearer with explicit variables.
8. Validate required operands before doing work
service=${1:-}
environment=${2:-}
if [[ -z $service || -z $environment ]]; then
printf 'usage: %s SERVICE ENVIRONMENT\n' "$0" >&2
exit 64
fi
case $environment in
dev|staging|prod) ;;
*) printf 'unsupported environment: %s\n' "$environment" >&2; exit 65 ;;
esacArgument-count validation checks shape; semantic validation checks whether values are actually acceptable.
9. Positional defaults work best for small interfaces
service=${1:-api}
environment=${2:-staging}
replicas=${3:-2}
printf 'service=%s env=%s replicas=%s\n' \
"$service" "$environment" "$replicas"As optional parameters multiply, named options are usually easier to remember and less error-prone than positional defaults.
10. Preserve the vector when wrapping another command
run_wrapped() {
printf 'running %d argument(s)\n' "$#" >&2
"$@"
}
run_wrapped printf 'service=%s env=%s\n' "api gateway" stagingExecuting "$@" is appropriate only when the function's contract is explicitly to execute a trusted command vector. Do not turn untrusted text into executable syntax.
11. Hands-on lab: positional deployment planner
mkdir -p "$HOME/devops-academy/bash/chapter06/lesson01"
cd "$HOME/devops-academy/bash/chapter06/lesson01"
cat > plan.sh <<'EOF'
#!/usr/bin/env bash
if (( $# < 2 || $# > 3 )); then
printf 'usage: %s SERVICE ENVIRONMENT [REPLICAS]\n' "$0" >&2
exit 64
fi
service=$1
environment=$2
replicas=${3:-2}
case $environment in
dev|staging|prod) ;;
*) printf 'invalid environment: %s\n' "$environment" >&2; exit 65 ;;
esac
if [[ ! $replicas =~ ^[0-9]+$ ]] || (( 10#$replicas < 1 )); then
printf 'replicas must be a positive integer\n' >&2
exit 66
fi
printf 'PLAN service=%s env=%s replicas=%d\n' \
"$service" "$environment" "$((10#$replicas))"
EOF
bash plan.sh "api gateway" staging 3
bash plan.sh worker prod
bash plan.sh api qa || trueVerification checklist
12. Knowledge check
Question 1. What does $# contain?
Question 2. Why is "$@" preferred for forwarding?
Question 3. What does $! contain?
Question 4. Is argument count enough validation?
13. Summary
Positional parameters are Bash's raw CLI interface. Use $# for arity, $0 for invocation naming, and quoted "$@" to preserve argument boundaries. Validate both shape and meaning before operational work.
14. Further reading
- GNU Bash Reference Manual — Special Parameters.
- GNU Bash Reference Manual — Shell Parameters.
- POSIX Shell Command Language — special parameters.
- ShellCheck documentation — argument forwarding and quoting.
Keep the academy open
Support free, practical DevOps education.
Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0
Send only Ethereum/ERC-20 compatible assets to this address.