阅读视图

发现新文章,点击刷新页面。

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:

sh
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:

~/flags.shsh
#!/bin/bash

while getopts "vh" opt; do
 case $opt in
 v) echo "Verbose mode enabled" ;;
 h) echo "Usage: $0 [-v] [-h]"; exit 0 ;;
 esac
done

Run the script:

Terminal
./flags.sh -v
./flags.sh -h
./flags.sh -vh
output
Verbose 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 -v for 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, getopts suppresses 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:

~/optarg_example.shsh
#!/bin/bash

while getopts "f:o:" opt; do
 case $opt in
 f) echo "Input file: $OPTARG" ;;
 o) echo "Output file: $OPTARG" ;;
 esac
done
Terminal
./optarg_example.sh -f data.csv -o results.txt
output
Input 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 $@:

~/optind_example.shsh
#!/bin/bash

while getopts "v" opt; do
 case $opt in
 v) echo "Verbose mode enabled" ;;
 esac
done

shift $((OPTIND - 1))

echo "Remaining arguments: $@"
Terminal
./optind_example.sh -v file1.txt file2.txt
output
Verbose 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:

~/verbose_errors.shsh
#!/bin/bash

while getopts "f:" opt; do
 case $opt in
 f) echo "File: $OPTARG" ;;
 esac
done
Terminal
./verbose_errors.sh -x
./verbose_errors.sh -f
output
./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, opt is set to ? and OPTARG contains the invalid option character.
  • For a missing argument, opt is set to : and OPTARG contains the option that was missing its argument.
~/silent_errors.shsh
#!/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
Terminal
./silent_errors.sh -x
./silent_errors.sh -f
output
Error: 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:

~/process.shsh
#!/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
done
Terminal
echo -e "line 1\nline 2\nline 3\nline 4\nline 5" > testfile.txt
./process.sh -v -n 3 testfile.txt
output
Verbose: 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:

~/deploy.shsh
#!/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
Terminal
./deploy.sh -e production -t v2.1.0 -d webapp
output
Deploying '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.

❌