How to Parse Command-Line Options in Bash with getopts
The getopts command is a Bash built-in that provides a clean, structured way to parse command-line options in your scripts. Instead of manually looping through arguments with case and shift, getopts handles option parsing, argument extraction, and error reporting for you.
This guide explains how to use getopts to process options and arguments in Bash scripts. If you are new to command-line arguments, start with our guide on positional parameters
.
Syntax
The basic syntax for getopts is:
while getopts "optstring" VARNAME; do
case $VARNAME in
# handle each option
esac
done-
optstring— A string that defines which options the script accepts -
VARNAME— A variable that holds the current option letter on each iteration
Each time the while loop runs, getopts processes the next option from the command line and stores the option letter in VARNAME. The loop ends when there are no more options to process.
Here is a simple example:
#!/bin/bash
while getopts "vh" opt; do
case $opt in
v) echo "Verbose mode enabled" ;;
h) echo "Usage: $0 [-v] [-h]"; exit 0 ;;
esac
doneRun the script:
./flags.sh -v
./flags.sh -h
./flags.sh -vhVerbose mode enabled
Usage: ./flags.sh [-v] [-h]
Verbose mode enabled
Usage: ./flags.sh [-v] [-h]
Notice that -vh works the same as -v -h. The getopts command automatically handles combined short options.
The Option String
The option string tells getopts which option letters are valid and which ones require an argument. There are three patterns:
-
f— A simple flag (no argument). Used for boolean switches like-vfor verbose. -
f:— An option that requires an argument. The colon after the letter means the user must provide a value, such as-f filename. -
:(leading colon) — Enables silent error mode. When placed at the very beginning of the option string,getoptssuppresses its default error messages so you can handle errors yourself.
For example, the option string ":vf:o:" means:
-
:— Silent error mode -
v— A simple flag (-v) -
f:— An option requiring an argument (-f filename) -
o:— An option requiring an argument (-o output)
OPTARG and OPTIND
When working with getopts, two special variables track the parsing state:
OPTARG
The OPTARG variable holds the argument value for options that require one. When you define an option with a trailing colon (e.g., f:), the value the user passes after -f is stored in OPTARG:
#!/bin/bash
while getopts "f:o:" opt; do
case $opt in
f) echo "Input file: $OPTARG" ;;
o) echo "Output file: $OPTARG" ;;
esac
done./optarg_example.sh -f data.csv -o results.txtInput file: data.csv
Output file: results.txt
OPTIND
The OPTIND variable holds the index of the next argument to be processed. It starts at 1 and increments as getopts processes each option. After the while loop finishes, OPTIND points to the first non-option argument.
Use shift $((OPTIND - 1)) after the loop to remove all processed options, leaving only the remaining positional arguments in $@:
#!/bin/bash
while getopts "v" opt; do
case $opt in
v) echo "Verbose mode enabled" ;;
esac
done
shift $((OPTIND - 1))
echo "Remaining arguments: $@"./optind_example.sh -v file1.txt file2.txtVerbose mode enabled
Remaining arguments: file1.txt file2.txt
The shift $((OPTIND - 1)) line is a common pattern. Without it, the processed options would still be part of the positional parameters, making it difficult to access the non-option arguments.
Error Handling
The getopts command has two error handling modes: verbose (default) and silent.
Verbose Mode (Default)
In verbose mode, getopts prints its own error messages when it encounters an invalid option or a missing argument:
#!/bin/bash
while getopts "f:" opt; do
case $opt in
f) echo "File: $OPTARG" ;;
esac
done./verbose_errors.sh -x
./verbose_errors.sh -f./verbose_errors.sh: illegal option -- x
./verbose_errors.sh: option requires an argument -- f
In this mode, getopts sets opt to ? for both invalid options and missing arguments.
Silent Mode
Silent mode is enabled by adding a colon at the beginning of the option string. In this mode, getopts suppresses its default error messages and gives you more control:
- For an invalid option,
optis set to?andOPTARGcontains the invalid option character. - For a missing argument,
optis set to:andOPTARGcontains the option that was missing its argument.
#!/bin/bash
while getopts ":f:vh" opt; do
case $opt in
f) echo "File: $OPTARG" ;;
v) echo "Verbose mode" ;;
h) echo "Usage: $0 [-v] [-f file] [-h]"; exit 0 ;;
\?) echo "Error: Invalid option -$OPTARG" >&2; exit 1 ;;
:) echo "Error: Option -$OPTARG requires an argument" >&2; exit 1 ;;
esac
done./silent_errors.sh -x
./silent_errors.sh -fError: Invalid option -x
Error: Option -f requires an argument
Silent mode is the recommended approach for production scripts because it allows you to write custom error messages that are more helpful to the user.
Practical Examples
Example 1: Script with Flags and Arguments
This script demonstrates a common pattern — a usage function , boolean flags, options with arguments, and input validation:
#!/bin/bash
usage() {
echo "Usage: $0 [-v] [-o output] [-n count] file..."
echo ""
echo "Options:"
echo " -v Enable verbose output"
echo " -o output Write results to output file"
echo " -n count Number of lines to process"
echo " -h Show this help message"
exit 1
}
VERBOSE=false
OUTPUT=""
COUNT=0
while getopts ":vo:n:h" opt; do
case $opt in
v) VERBOSE=true ;;
o) OUTPUT="$OPTARG" ;;
n) COUNT="$OPTARG" ;;
h) usage ;;
\?) echo "Error: Invalid option -$OPTARG" >&2; usage ;;
:) echo "Error: Option -$OPTARG requires an argument" >&2; usage ;;
esac
done
shift $((OPTIND - 1))
if [ $# -eq 0 ]; then
echo "Error: No input files specified" >&2
usage
fi
if [ "$VERBOSE" = true ]; then
echo "Verbose: ON"
echo "Output: ${OUTPUT:-stdout}"
echo "Count: ${COUNT:-all}"
echo "Files: $@"
echo ""
fi
for file in "$@"; do
if [ ! -f "$file" ]; then
echo "Warning: '$file' not found, skipping" >&2
continue
fi
if [ -n "$OUTPUT" ]; then
if [ "$COUNT" -gt 0 ] 2>/dev/null; then
head -n "$COUNT" "$file" >> "$OUTPUT"
else
cat "$file" >> "$OUTPUT"
fi
else
if [ "$COUNT" -gt 0 ] 2>/dev/null; then
head -n "$COUNT" "$file"
else
cat "$file"
fi
fi
doneecho -e "line 1\nline 2\nline 3\nline 4\nline 5" > testfile.txt
./process.sh -v -n 3 testfile.txtVerbose: ON
Output: stdout
Count: 3
Files: testfile.txt
line 1
line 2
line 3
The script parses the options first, then uses shift to access the remaining file arguments.
Example 2: Configuration Wrapper
This example shows a script that wraps another command, passing different configurations based on the options provided:
#!/bin/bash
ENV="staging"
DRY_RUN=false
TAG="latest"
while getopts ":e:t:dh" opt; do
case $opt in
e) ENV="$OPTARG" ;;
t) TAG="$OPTARG" ;;
d) DRY_RUN=true ;;
h)
echo "Usage: $0 [-e environment] [-t tag] [-d] service"
echo ""
echo " -e env Target environment (default: staging)"
echo " -t tag Image tag (default: latest)"
echo " -d Dry run mode"
exit 0
;;
\?) echo "Error: Invalid option -$OPTARG" >&2; exit 1 ;;
:) echo "Error: Option -$OPTARG requires an argument" >&2; exit 1 ;;
esac
done
shift $((OPTIND - 1))
SERVICE="${1:?Error: Service name required}"
echo "Deploying '$SERVICE' to $ENV with tag '$TAG'"
if [ "$DRY_RUN" = true ]; then
echo "[DRY RUN] Would execute: docker pull myregistry/$SERVICE:$TAG"
echo "[DRY RUN] Would execute: kubectl set image deployment/$SERVICE $SERVICE=myregistry/$SERVICE:$TAG -n $ENV"
else
echo "Pulling image and updating deployment..."
fi./deploy.sh -e production -t v2.1.0 -d webappDeploying 'webapp' to production with tag 'v2.1.0'
[DRY RUN] Would execute: docker pull myregistry/webapp:v2.1.0
[DRY RUN] Would execute: kubectl set image deployment/webapp webapp=myregistry/webapp:v2.1.0 -n production
getopts vs getopt
The getopts built-in is often confused with the external getopt command. Here are the key differences:
| Feature |
getopts (built-in) |
getopt (external) |
|---|---|---|
| Type | Bash/POSIX built-in | External program (/usr/bin/getopt) |
| Long options | Not supported | Supported (--verbose) |
| Portability | Works on all POSIX shells | Varies by OS (GNU vs BSD) |
| Whitespace handling | Handles correctly | GNU version handles correctly |
| Error handling | Built-in verbose/silent modes | Manual |
| Speed | Faster (no subprocess) | Slower (spawns a process) |
Use getopts when you only need short options and want maximum portability. Use getopt (GNU version) when you need long options like --verbose or --output.
Troubleshooting
Options are not being parsed
Make sure your options come before any non-option arguments. The getopts command stops parsing when it encounters the first non-option argument. For example, ./script.sh file.txt -v will not parse -v because file.txt comes first.
OPTIND is not resetting between function calls
If you use getopts inside a function
and call that function multiple times, you need to reset OPTIND to 1 before each call. Otherwise, getopts will start from where it left off.
Missing argument not detected
If an option requires an argument but getopts does not report an error, check that you included a colon after the option letter in the option string. For example, use "f:" instead of "f" if -f needs an argument.
Unexpected ? in the variable
In verbose mode (no leading colon), getopts sets the variable to ? for both invalid options and missing arguments. Switch to silent mode (leading colon) to distinguish between the two cases and write custom error messages.
Quick Reference
| Element | Description |
|---|---|
getopts "opts" var |
Parse options defined in opts, store current letter in var
|
f in option string |
Simple flag, no argument |
f: in option string |
Option that requires an argument |
: (leading) |
Enable silent error mode |
$OPTARG |
Holds the argument for the current option |
$OPTIND |
Index of the next argument to process |
shift $((OPTIND - 1)) |
Remove parsed options, keep remaining arguments |
\? in case |
Handles invalid options |
: in case |
Handles missing arguments (silent mode only) |
FAQ
Does getopts support long options like –verbose?
No. The getopts built-in only supports single-character options (e.g., -v). For long options, use the external getopt command (GNU version) or parse them manually with a case statement
and shift.
Can I combine options like -vf file?
Yes. The getopts command automatically handles combined options. When it encounters -vf file, it processes -v first, then -f with file as its argument.
What happens if I forget shift $((OPTIND - 1))?
The processed options will remain in the positional parameters. Any code that accesses $1, $2, or $@ after the getopts loop will still see the option flags instead of just the remaining arguments.
Is getopts POSIX compliant?
Yes. The getopts command is defined by the POSIX standard and works in all POSIX-compliant shells, including bash, dash, ksh, and zsh. This makes it more portable than the external getopt command.
How do I make an option’s argument optional?
The getopts built-in does not support optional arguments for options. An option either always requires an argument (using :) or never takes one. If you need optional arguments, handle the logic manually after parsing.
Conclusion
The getopts built-in provides a reliable way to parse command-line options in Bash scripts. It handles option string definitions, argument extraction, combined flags, and error reporting with minimal code.
If you have any questions, feel free to leave a comment below.
