Shell builtins · bash builtin
getopts
Parse short command-line options such as -v and -o FILE in a script, one option per call, inside a while loop.
getopts OPTSTRING NAME [ARG...]
getopts is the shell's built-in option parser. You describe the options in an option
string: each letter is an option, and a letter followed by : takes an argument. Called in
a while loop, each call puts the next option letter into a variable, its argument (if any)
into OPTARG, and returns non-zero when the options run out. OPTIND is left pointing at
the first non-option argument, so shift $((OPTIND - 1)) leaves only the operands in $@.
It handles the details users expect: bundled flags (-vn), arguments attached or separate
(-ofile or -o file), and -- to end options. A leading : in the option string turns on
silent mode, where getopts prints no messages and instead reports problems through the
variable (? for an unknown option, : for a missing argument) so the script can word its
own errors.
What it does not do: long options like --verbose, and options after the first operand
(script file -v stops at file). For long options either parse by hand with a case loop
over $1 and shift, or use the external getopt from util-linux. getopts is POSIX and
works the same in dash.
Examples
A complete script with getopts
#!/usr/bin/env bash
usage() { echo "usage: ${0##*/} [-v] [-n] [-o DIR] SOURCE..." >&2; exit 2; }
verbose=0 dry_run=0 outdir=.
while getopts ':vno:h' opt; do
case $opt in
v) verbose=1 ;;
n) dry_run=1 ;;
o) outdir=$OPTARG ;;
h) usage ;;
:) echo "error: -$OPTARG needs an argument" >&2; usage ;;
?) echo "error: unknown option -$OPTARG" >&2; usage ;;
esac
done
shift $((OPTIND - 1))
(( $# > 0 )) || usage
echo "verbose=$verbose dry_run=$dry_run outdir=$outdir sources=($*)"
chmod +x backup.sh
./backup.sh -v -o /tmp/out data logs
./backup.sh data
verbose=1 dry_run=0 outdir=/tmp/out sources=(data logs) verbose=0 dry_run=0 outdir=. sources=(data)
The option string :vno:h declares -v, -n and -h as flags and -o as taking an
argument; the leading : selects silent error handling. After the loop, shift removes
the parsed options so $@ holds only the sources. With no options the loop body never
runs and the defaults stand.
Bundled flags, attached arguments and --
#!/usr/bin/env bash
usage() { echo "usage: ${0##*/} [-v] [-n] [-o DIR] SOURCE..." >&2; exit 2; }
verbose=0 dry_run=0 outdir=.
while getopts ':vno:h' opt; do
case $opt in
v) verbose=1 ;;
n) dry_run=1 ;;
o) outdir=$OPTARG ;;
h) usage ;;
:) echo "error: -$OPTARG needs an argument" >&2; usage ;;
?) echo "error: unknown option -$OPTARG" >&2; usage ;;
esac
done
shift $((OPTIND - 1))
(( $# > 0 )) || usage
echo "verbose=$verbose dry_run=$dry_run outdir=$outdir sources=($*)"
bash backup.sh -vn -o/srv/backups data
bash backup.sh -n -o "dir with spaces" data
bash backup.sh -v -- -weird-dir data
verbose=1 dry_run=1 outdir=/srv/backups sources=(data) verbose=0 dry_run=1 outdir=dir with spaces sources=(data) verbose=1 dry_run=0 outdir=. sources=(-weird-dir data)
-vn is split into -v and -n, and -o/srv/backups is -o with an attached
argument. A quoted argument with spaces arrives intact in OPTARG. -- ends the
options and is consumed, so -weird-dir is treated as a source.
Unknown options, missing arguments and no operands
#!/usr/bin/env bash
usage() { echo "usage: ${0##*/} [-v] [-n] [-o DIR] SOURCE..." >&2; exit 2; }
verbose=0 dry_run=0 outdir=.
while getopts ':vno:h' opt; do
case $opt in
v) verbose=1 ;;
n) dry_run=1 ;;
o) outdir=$OPTARG ;;
h) usage ;;
:) echo "error: -$OPTARG needs an argument" >&2; usage ;;
?) echo "error: unknown option -$OPTARG" >&2; usage ;;
esac
done
shift $((OPTIND - 1))
(( $# > 0 )) || usage
echo "verbose=$verbose dry_run=$dry_run outdir=$outdir sources=($*)"
bash backup.sh -x data; echo "exit $?"
bash backup.sh data -o; echo "exit $?"
bash backup.sh -v -o; echo "exit $?"
bash backup.sh -v; echo "exit $?"
error: unknown option -x usage: backup.sh [-v] [-n] [-o DIR] SOURCE... exit 2 verbose=0 dry_run=0 outdir=. sources=(data -o) exit 0 error: -o needs an argument usage: backup.sh [-v] [-n] [-o DIR] SOURCE... exit 2 usage: backup.sh [-v] [-n] [-o DIR] SOURCE... exit 2
In silent mode an unknown option sets opt to ? and a missing argument sets it to :,
with the letter in OPTARG either way, so the script words its own errors. The second
line is a trap: parsing stops at the first operand, data, so -o is never seen as an
option and is accepted as a second source. The last call has options but no sources, so
the (( $# > 0 )) check prints the usage line.
Default error messages without the leading colon
set -- -a -z -b
while getopts 'ab:' opt; do echo "opt=$opt OPTARG=${OPTARG-unset}"; done
opt=a OPTARG=unset /bin/bash: illegal option -- z opt=? OPTARG=unset /bin/bash: option requires an argument -- b opt=? OPTARG=unset
Without the leading :, getopts prints its own message for the unknown -z and for
-b, which needs an argument and has none. Both set opt to ? and leave OPTARG
unset, so the script cannot tell the two cases apart. Silent mode fixes both problems.
Collect a repeatable option and count a flag
set -- -v -v -e ERROR -e WARN logs/app.log
verbosity=0 patterns=()
while getopts 've:' opt; do
case $opt in
v) verbosity=$((verbosity + 1)) ;;
e) patterns+=("$OPTARG") ;;
esac
done
shift $((OPTIND - 1))
echo "verbosity=$verbosity patterns=${patterns[*]} file=$1"
verbosity=2 patterns=ERROR WARN file=logs/app.log
getopts reports an option each time it appears, so a counter gives -v -v (or -vv)
a level, and appending to an array collects every -e. This mirrors how grep -e and
ssh -v behave.
Watch OPTIND advance
set -- -v -o out.txt input.csv
while getopts 'vo:' opt; do echo "got -$opt, OPTIND now $OPTIND"; done
shift $((OPTIND - 1))
echo "remaining: $*"
got -v, OPTIND now 2 got -o, OPTIND now 4 remaining: input.csv
OPTIND is the index of the next argument to look at. -o out.txt consumes two
arguments, so it jumps from 2 to 4. shift $((OPTIND - 1)) drops exactly the parsed
options.
The same loop works in sh
set -- -q -n 3 file.txt
quiet=no count=1
while getopts 'qn:' opt; do
case $opt in
q) quiet=yes ;;
n) count=$OPTARG ;;
*) exit 2 ;;
esac
done
shift $((OPTIND - 1))
echo "quiet=$quiet count=$count file=$1"
quiet=yes count=3 file=file.txt
This runs under dash, the /bin/sh on Debian and Ubuntu. getopts is POSIX, so option
parsing is one of the few areas where a #!/bin/sh script gives up nothing compared to
bash.
Reset OPTIND when using getopts in a function
f() {
local OPTIND opt
while getopts 'q' opt; do echo " option -$opt"; done
shift $((OPTIND - 1)); echo " args: $*"
}
echo "first call:"; f -q one
echo "second call:"; f -q two
first call: option -q args: one second call: option -q args: two
OPTIND is a global that getopts does not reset. Making it local gives each call a
fresh start; without it, the second call would begin at index 2 and miss -q.
Common pitfalls
Forgetting
shift $((OPTIND - 1))after the loop:$1is still the first option, not the first file.Options after operands are not parsed:
script file -vtreats-vas an operand. Document that options come first, or parse by hand.Reusing getopts in a function or a second loop without resetting
OPTIND=1(or making itlocal): parsing starts where the last loop left off.getopts has no long options. Do not confuse it with
getopt(no s), an external util-linux program that supports--longoptions but needs careful quoting witheval set --.
GNU vs BSD and macOS
The examples above ran the GNU/Linux version. These are the differences that matter on macOS and the BSDs (written from their documentation, not run here).
getoptsis POSIX and identical in bash, dash, zsh and ksh, on Linux and macOS. The externalgetoptdiffers: the util-linux one on Linux supports long options, the BSD/macOS one does not and mangles arguments with spaces.
Related
shiftDrop the first N positional parameters ($1, $2, ...) and renumber the rest down.setTurn shell options such as -e, -u, -x and pipefail on or off, and replace the positional parameters.readRead one line from standard input and split it into shell variables.- Script argumentsPositional parameters $1 to ${10}, $# and $0, why "$@" is almost always right and "$*" rarely, shift, getopts, and ending options with --.
- The case statementMatching one value against glob patterns with case, alternatives with |, the default branch, fall-through with ;& and ;;&, and parsing options with case.
- Loopsfor over lists, files and ranges, C-style for, while and until, break and continue, retry loops, and redirecting a whole loop's input or output.