Command-line tools that behave like real Unix commands are easier to use and to call from other scripts.
getopts optstring var parses short options one at a time in a while loop. Each letter in the optstring is an accepted option; a letter followed by : takes a value, which arrives in $OPTARG. A leading : switches to silent error mode, where you handle problems yourself: an unknown option sets var to ? and a missing value sets it to :, with the offending letter in $OPTARG.
getopts handles bundling (-vf), attached values (-ofile) and stops at the first non-option or at --. After the loop, shift $((OPTIND - 1)) removes the parsed options so "$@" holds only the operands (files, names...).
Good habits for script interfaces:
- a
usagefunction that prints to stderr and exits with status 2, - defaults set before the loop,
- validate operands after shifting (
(( $# > 0 )) || usage), - support
-hfor help.
getopts is a bash builtin and POSIX, so it is portable, but it only understands short options. For --long-options, either parse manually with a while (( $# )); do case $1 in ... loop plus shift, or use the util-linux getopt program (note: no s), which is available on Linux but differs on macOS.
Example
usage() { echo "usage: tool [-v] [-o FILE] NAME..." >&2; return 2; }
tool() {
local verbose=0 output=- opt OPTIND=1
while getopts ':vo:h' opt; do
case $opt in
v) verbose=1 ;;
o) output=$OPTARG ;;
h) usage; return ;;
:) echo "-$OPTARG needs a value" >&2; usage; return ;;
\?) echo "unknown option -$OPTARG" >&2; usage; return ;;
esac
done
shift $((OPTIND - 1)) # "$@" is now just the operands
(( $# > 0 )) || { usage; return; }
echo "verbose=$verbose output=$output names=$*"
}
tool -v -o out.txt a b # verbose=1 output=out.txt names=a b
tool -vo out.txt a # verbose=1 output=out.txt names=a (bundled)
tool -x a # unknown option -x / usage: ...
tool -o # -o needs a value / usage: ...Remember
- Letters in the optstring are options; a trailing : means it takes a value in $OPTARG
- shift $((OPTIND - 1)) leaves only the operands in "$@"
- getopts is short-options only; parse --long options manually
Watch out for
- Forgetting the shift after the loop, so the options are processed again as file names.