Chapter 06Lesson 01~65 minutes

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.

BeginnerCLI designHands-on lab

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

ParameterMeaningTypical use
$0Invocation name or script pathUseful in usage messages
$#Number of positional argumentsUse for arity checks
${10}Tenth positional argumentBraces are required beyond $9
if (( $# < 2 )); then
  printf 'usage: %s SERVICE ENVIRONMENT\n' "$0" >&2
  exit 64
fi

3. 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"
Forwarding rule

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

ParameterMeaningUse
$$Shell process identifierUseful for diagnostics; BASHPID is also available in Bash
$!PID of most recent background commandUse with wait
sleep 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 ;;
esac

Argument-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" staging
Execution boundary

Executing "$@" 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 || true

Verification 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.
Next lesson

shift and Robust Argument Processing

Continue Chapter 6 by refining the command-line interface and its validation contract.

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.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0 Send only Ethereum/ERC-20 compatible assets to this address.