Named options make a shell script easier to automate because callers do not have to remember a fragile positional order. The Bash getopts builtin handles short options one at a time while leaving later file operands available to the script.
Each successful getopts call stores the option letter in a variable, places a required option value in OPTARG, and advances OPTIND to the next argument. A leading colon in the option string enables silent error handling, so the script can print a specific message for an unknown option or a missing value.
The parser accepts -n NAME for a required archive label, -d for dry-run mode, and -h for help. A -- marker ends option parsing explicitly, which allows a remaining file operand such as -old.log to begin with a hyphen.
Related: How to create and run a Bash script
Related: How to use a case statement in Bash
Steps to parse command line options in Bash with getopts:
- Create archive-plan.sh with its interpreter line, usage function, and default values.
- archive-plan.sh
#!/usr/bin/env bash set -u usage() { printf 'Usage: %s -n NAME [-d] [FILE...]\n' "${0##*/}" } name= dry_run=0
The name variable starts empty because -n is required, while dry_run starts at 0 because -d is optional.
- Add the getopts loop after the default values.
while getopts ':n:dh' option; do case $option in n) name=$OPTARG ;; d) dry_run=1 ;; h) usage; exit 0 ;; :) printf 'Missing value for -%s\n' "$OPTARG" >&2; usage >&2; exit 2 ;; \?) printf 'Unknown option: -%s\n' "$OPTARG" >&2; usage >&2; exit 2 ;; esac done
In :n:dh, the first colon enables silent error handling and the colon after n marks -n as requiring a value. The d and h options do not take values.
- Add the operand shift and required-name check below the option loop.
shift "$((OPTIND - 1))" if [[ -z $name ]]; then printf 'Missing required option: -n\n' >&2 usage >&2 exit 2 fi
OPTIND points to the first unparsed argument. Shifting by one less than that index removes the recognized options and their values while preserving later file operands.
- Append the parsed-value output after the required-name check.
printf 'archive=%s\n' "$name" printf 'dry_run=%s\n' "$dry_run" for file in "$@"; do printf 'input=%s\n' "$file" done
Quoted "$@" expansion passes each remaining operand as one value, including names that contain spaces.
- Compare archive-plan.sh with the consolidated file.
- archive-plan.sh
#!/usr/bin/env bash set -u usage() { printf 'Usage: %s -n NAME [-d] [FILE...]\n' "${0##*/}" } name= dry_run=0 while getopts ':n:dh' option; do case $option in n) name=$OPTARG ;; d) dry_run=1 ;; h) usage; exit 0 ;; :) printf 'Missing value for -%s\n' "$OPTARG" >&2; usage >&2; exit 2 ;; \?) printf 'Unknown option: -%s\n' "$OPTARG" >&2; usage >&2; exit 2 ;; esac done shift "$((OPTIND - 1))" if [[ -z $name ]]; then printf 'Missing required option: -n\n' >&2 usage >&2 exit 2 fi printf 'archive=%s\n' "$name" printf 'dry_run=%s\n' "$dry_run" for file in "$@"; do printf 'input=%s\n' "$file" done
- Check archive-plan.sh for Bash syntax errors.
$ bash -n archive-plan.sh
No output means Bash parsed the complete file without finding a syntax error.
- Confirm that -n without a value reaches the missing-value branch.
$ bash archive-plan.sh -n Missing value for -n Usage: archive-plan.sh -n NAME [-d] [FILE...]
The script exits with status 2, allowing a caller to distinguish invalid usage from a successful parse.
- Run the parser with both supported options and two file operands.
$ bash archive-plan.sh -d -n nightly -- app.log -old.log archive=nightly dry_run=1 input=app.log input=-old.log
The -- marker prevents -old.log from being read as another option. Both input= lines confirm that the shift retained the operands after parsing.
Mohd Shakir Zakaria is a cloud architect with deep roots in software development and open-source advocacy. Certified in AWS, Red Hat, VMware, ITIL, and Linux, he specializes in designing and managing robust cloud and on-premises infrastructures.