shift and Robust Argument Processing
As command lines grow, it is useful to consume already-processed arguments and leave the remainder in a predictable state. Bash's `shift` is the core mechanism for that style of parser.
Learning objectives
By the end of this lesson
- Explain how shift renumbers parameters.
- Consume options and values safely.
- Support the -- end-of-options convention.
- Route subcommands with remaining arguments.
- Avoid eval and shell-string command construction.
1. shift removes the first positional parameter
set -- alpha beta gamma
printf 'before count=%d first=%s\n' "$#" "$1"
shift
printf 'after count=%d first=%s\n' "$#" "$1"$0 is not shifted; only the positional parameters are renumbered.
2. shift N consumes several parameters
set -- one two three four
shift 2
printf 'remaining=%d first=%s\n' "$#" "$1"Do not shift past the available arguments. For options requiring values, validate the count before shift 2.
3. Parse one token at a time with case
while (( $# > 0 )); do
case $1 in
--verbose)
verbose=true
shift
;;
--environment)
(( $# >= 2 )) || { printf 'missing value\n' >&2; exit 64; }
environment=$2
shift 2
;;
*)
break
;;
esac
done4. -- conventionally ends option parsing
case $1 in
--)
shift
break
;;
esacSupporting -- lets users pass operands that begin with a dash without having them mistaken for options.
5. Unknown options should fail closed
case $1 in
--verbose|--dry-run) ;;
-*)
printf 'unknown option: %s\n' "$1" >&2
exit 64
;;
esacSilently ignoring an option typo can change behavior in dangerous ways.
6. Manual parsing can support --name=value
case $1 in
--environment=*)
environment=${1#*=}
[[ -n $environment ]] || {
printf 'empty environment\n' >&2
exit 64
}
shift
;;
esac7. Consume the subcommand and delegate
subcommand=${1:-help}
shift || true
case $subcommand in
deploy) deploy_command "$@" ;;
status) status_command "$@" ;;
help) usage ;;
*) printf 'unknown command: %s\n' "$subcommand" >&2; exit 64 ;;
esacEach subcommand can own a smaller argument grammar instead of sharing one oversized global parser.
8. Build downstream command vectors with arrays
curl_args=(--fail --silent --show-error)
[[ $verbose == true ]] && curl_args+=(--verbose)
curl "${curl_args[@]}" "$url"Arrays preserve one option or operand per element and remain safe when values contain spaces or wildcard characters.
9. Do not parse by reconstructing a command string
# Avoid:
# command_line="$*"
# eval "$command_line"
# Preserve the vector instead:
target_command "$@"Using eval performs another shell parse and can turn data into syntax. Arrays and quoted "$@" avoid that class of error.
10. Treat leftovers as explicit operands
service=${1:-}
shift || true
if (( $# > 0 )); then
printf 'unexpected extra operand: %s\n' "$1" >&2
exit 64
fiA robust parser knows whether zero, one, or many operands should remain after options are consumed.
11. Hands-on lab: long-option parser
mkdir -p "$HOME/devops-academy/bash/chapter06/lesson02"
cd "$HOME/devops-academy/bash/chapter06/lesson02"
cat > deploy-cli.sh <<'EOF'
#!/usr/bin/env bash
environment=staging
dry_run=false
replicas=2
while (( $# > 0 )); do
case $1 in
--environment)
(( $# >= 2 )) || { printf 'missing environment\n' >&2; exit 64; }
environment=$2
shift 2
;;
--replicas=*)
replicas=${1#*=}
shift
;;
--dry-run)
dry_run=true
shift
;;
--)
shift
break
;;
-*)
printf 'unknown option: %s\n' "$1" >&2
exit 64
;;
*)
break
;;
esac
done
service=${1:-}
[[ -n $service ]] || { printf 'SERVICE required\n' >&2; exit 64; }
case $environment in dev|staging|prod) ;; *) exit 65 ;; esac
[[ $replicas =~ ^[0-9]+$ ]] || exit 66
printf 'PLAN service=%s env=%s replicas=%s dry_run=%s\n' \
"$service" "$environment" "$replicas" "$dry_run"
EOF
bash deploy-cli.sh --environment prod --replicas=3 --dry-run api
bash deploy-cli.sh -- --strange-service
bash deploy-cli.sh --unknown api || trueVerification checklist
12. Knowledge check
Question 1. What does shift do?
Question 2. Why support --?
Question 3. Why reject unknown options?
Question 4. Why prefer arrays to command strings?
13. Summary
shift enables clear left-to-right argument consumption. Combined with case, it supports long options, subcommands, and the -- convention. Keep argument vectors intact and never use eval as a general parser.
14. Further reading
- GNU Bash Reference Manual —
shift. - GNU Bash Reference Manual — Positional Parameters.
- POSIX Utility Syntax Guidelines.
- ShellCheck documentation — argument parsing and eval hazards.
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.