Bash getopts: Command-Line Argument Parsing
Every useful command-line tool needs to accept input. The naive approach uses positional parameters (`$1`, `$2`, etc.), but this breaks down quickly. Consider a backup script:
Key Insights
getoptsis bash’s built-in POSIX-compliant parser that handles short options (-v,-o file) without external dependencies, making it the most portable choice for shell scripts- The optstring format controls behavior: colons after letters indicate required arguments, while a leading colon enables silent error mode for custom error handling
getoptsonly supports short options (single dash, single character), but this limitation is often acceptable for shell scripts where simplicity and portability matter more than feature completeness
Why Command-Line Argument Parsing Matters
Every useful command-line tool needs to accept input. The naive approach uses positional parameters ($1, $2, etc.), but this breaks down quickly. Consider a backup script:
#!/bin/bash
# Fragile positional approach
SOURCE=$1
DEST=$2
COMPRESS=$3
rsync -av $SOURCE $DEST
This forces users to remember the exact order and provide empty strings for optional parameters: ./backup.sh /data /backup "". It’s inflexible and error-prone.
Options (flags) solve this by providing named parameters: ./backup.sh -s /data -d /backup -c. Users can specify them in any order, omit optional ones, and the intent is immediately clear. This is where getopts comes in—it’s bash’s built-in parser for handling these options cleanly.
Basic getopts Syntax
The getopts built-in follows a specific pattern. The syntax is getopts optstring name, where optstring defines valid options and name is the variable that receives each option.
Here’s the fundamental structure:
#!/bin/bash
while getopts "vh" opt; do
case $opt in
v)
VERBOSE=true
;;
h)
echo "Usage: $0 [-v] [-h]"
echo " -v Enable verbose output"
echo " -h Show this help message"
exit 0
;;
\?)
echo "Invalid option: -$OPTARG" >&2
exit 1
;;
esac
done
The optstring "vh" declares two valid options: -v and -h. The getopts command processes arguments one at a time in a while loop. Each iteration sets $opt to the current option letter. The case statement handles each option appropriately.
Two special variables are critical:
OPTARG: Contains the argument value for options that require oneOPTIND: Tracks the index of the next argument to process
The \?) case catches invalid options automatically. When getopts encounters an unknown option, it sets opt to ? and stores the invalid option letter in OPTARG.
Options with Arguments
Many options need values. In the optstring, a colon after a letter indicates that option requires an argument. The argument value is accessible via $OPTARG.
#!/bin/bash
OUTPUT=""
VERBOSE=false
COUNT=10
while getopts "o:vn:" opt; do
case $opt in
o)
OUTPUT=$OPTARG
;;
v)
VERBOSE=true
;;
n)
COUNT=$OPTARG
;;
\?)
echo "Invalid option: -$OPTARG" >&2
exit 1
;;
esac
done
echo "Output file: ${OUTPUT:-stdout}"
echo "Verbose: $VERBOSE"
echo "Count: $COUNT"
The optstring "o:vn:" declares:
-orequires an argument (filename)-vis a boolean flag-nrequires an argument (number)
Usage examples:
./script.sh -o output.txt -v -n 5
./script.sh -v -n 100 -o results.log
./script.sh -ovn 20 output.txt # Compact form
The compact form bundles options together. For options with arguments, the argument can immediately follow or be the next parameter. Both -ooutput.txt and -o output.txt work identically.
Error Handling
By default, getopts prints error messages for invalid options and missing arguments. For production scripts, you want custom error handling. A leading colon in the optstring enables silent mode:
#!/bin/bash
while getopts ":o:vn:" opt; do
case $opt in
o)
OUTPUT=$OPTARG
;;
v)
VERBOSE=true
;;
n)
if ! [[ $OPTARG =~ ^[0-9]+$ ]]; then
echo "Error: -n requires a numeric argument" >&2
exit 1
fi
COUNT=$OPTARG
;;
:)
echo "Error: -$OPTARG requires an argument" >&2
exit 1
;;
\?)
echo "Error: Invalid option -$OPTARG" >&2
echo "Usage: $0 [-o file] [-v] [-n count]" >&2
exit 1
;;
esac
done
The leading colon in ":o:vn:" changes behavior:
- Invalid options set
optto?(same as before) - Missing required arguments set
optto:(instead of printing an error)
The :) case handles missing arguments. When a user runs ./script.sh -o without specifying a filename, getopts sets opt to : and OPTARG to o (the option that’s missing its argument).
This example also validates the numeric argument for -n, demonstrating that you should validate option values beyond what getopts provides.
Real-World Example: Deployment Script
Here’s a complete deployment script that demonstrates getopts in practice:
#!/bin/bash
set -euo pipefail
# Default values
ENVIRONMENT="staging"
CONFIG_FILE=""
DRY_RUN=false
VERBOSE=false
show_help() {
cat << EOF
Usage: $0 [OPTIONS]
Deploy application to specified environment
OPTIONS:
-e ENV Target environment (staging|production) [default: staging]
-c FILE Configuration file path (required)
-d Dry run - show what would be deployed
-v Verbose output
-h Show this help message
EXAMPLES:
$0 -e production -c config/prod.yml
$0 -c config/staging.yml -d -v
EOF
}
while getopts ":e:c:dvh" opt; do
case $opt in
e)
ENVIRONMENT=$OPTARG
if [[ ! "$ENVIRONMENT" =~ ^(staging|production)$ ]]; then
echo "Error: Environment must be 'staging' or 'production'" >&2
exit 1
fi
;;
c)
CONFIG_FILE=$OPTARG
if [[ ! -f "$CONFIG_FILE" ]]; then
echo "Error: Config file '$CONFIG_FILE' not found" >&2
exit 1
fi
;;
d)
DRY_RUN=true
;;
v)
VERBOSE=true
;;
h)
show_help
exit 0
;;
:)
echo "Error: -$OPTARG requires an argument" >&2
show_help
exit 1
;;
\?)
echo "Error: Invalid option -$OPTARG" >&2
show_help
exit 1
;;
esac
done
# Validate required options
if [[ -z "$CONFIG_FILE" ]]; then
echo "Error: Configuration file (-c) is required" >&2
show_help
exit 1
fi
# Process remaining positional arguments
shift $((OPTIND - 1))
if [[ $VERBOSE == true ]]; then
echo "Environment: $ENVIRONMENT"
echo "Config: $CONFIG_FILE"
echo "Dry run: $DRY_RUN"
fi
if [[ $DRY_RUN == true ]]; then
echo "[DRY RUN] Would deploy to $ENVIRONMENT using $CONFIG_FILE"
else
echo "Deploying to $ENVIRONMENT..."
# Actual deployment logic here
fi
The shift $((OPTIND - 1)) line is crucial. After getopts finishes, OPTIND points to the first non-option argument. Shifting removes all processed options, leaving only positional arguments in $1, $2, etc. This is essential if your script accepts both options and positional parameters.
Limitations and Alternatives
getopts has constraints you should understand:
No long options: You can’t use --verbose or --output=file.txt. Only single-character options work. For many shell scripts, this is acceptable—short options are actually faster to type for frequently-used tools.
POSIX only: getopts strictly follows POSIX conventions. Options must come before positional arguments. ./script.sh file.txt -v won’t work; it must be ./script.sh -v file.txt.
No built-in required option checking: You must manually verify that required options were provided.
For long option support, use the external getopt command (note: getopt, not getopts):
#!/bin/bash
# Using getopt (external command) for long options
PARSED=$(getopt -o v,o: --long verbose,output: -n "$0" -- "$@")
eval set -- "$PARSED"
while true; do
case "$1" in
-v|--verbose)
VERBOSE=true
shift
;;
-o|--output)
OUTPUT=$2
shift 2
;;
--)
shift
break
;;
esac
done
The getopt command is more powerful but less portable. Enhanced getopt (from util-linux) isn’t available on all systems, particularly macOS without GNU coreutils. If portability matters, stick with getopts.
For complex CLIs with subcommands, validation rules, and extensive help text, consider writing in Python with argparse or Go with cobra instead of bash. Shell scripts excel at simple automation, not building complex user interfaces.
Conclusion
Use getopts for shell scripts that need clean option parsing without external dependencies. It handles 90% of use cases: flags, options with arguments, error handling, and help messages. The pattern is consistent and readable once you understand the optstring syntax.
Start every option-parsing script with a leading colon for silent mode, implement custom error messages, validate option values, and always provide a help option. Your users—and your future self—will appreciate the clarity.