Step 1 of 7 · Reading · ~4 min
Learn
Command Substitution and Exit Status
getopts
By the end of this lesson you can make a script accept options such as -q and -o file the standard way, handle bad usage with a clear message, and separate options from the operands that follow them. getopts is bash's built-in parser for short, single-letter options, and nearly every serious script uses it.
Positional parameters first
A script's arguments arrive as $1, $2, and so on. $# is how many there are, "$@" is all of them, and shift throws away $1 and moves the rest down one place:
set -- -q -o out.txt a.txt b.txt # set the argument list by hand
echo "$# arguments, first is $1" # 5 arguments, first is -q
shift
echo "$1" # -o
set -- replaces the argument list, which is useful for testing. On this platform a script is started with no arguments, so the exercises read a line from stdin and install it with set --. getopts cannot tell the difference.
The basics
#!/usr/bin/env bash
quiet=0
out=""
while getopts "qo:" opt; do
case $opt in
q) quiet=1 ;;
o) out=$OPTARG ;;
*) echo "usage: $0 [-q] [-o file] paths..." >&2; exit 1 ;;
esac
done
shift $((OPTIND - 1))
echo "quiet=$quiet out=$out paths: $*"
With the arguments -q -o out.txt a.txt b.txt this prints quiet=1 out=out.txt paths: a.txt b.txt. The pieces:
- The option string
"qo:"lists the letters. A letter followed by a colon takes a value; a bare letter is a flag. getoptsreturns success once per option and fails when the options run out, so it is the condition of awhileloop. The current letter lands in the variable you name (opt).$OPTARGholds the value of an option that takes one.$OPTINDis the index of the next argument to process. After the loop,shift $((OPTIND - 1))discards the options, leaving just the operands in"$@".
What it accepts, following the POSIX rules: flags can be combined (-qo out.txt), a value may be attached (-oout.txt), and parsing stops at the first argument that is not an option. In a.txt -q, the -q is treated as an ordinary operand. A bare -- also ends the options, which is how you pass an operand that starts with a dash.
Reporting bad usage
If the option string starts with a colon, getopts runs in silent mode: it prints nothing itself, so your script decides what to say. Two special values then appear in opt:
while getopts ":qo:" opt; do
case $opt in
q) quiet=1 ;;
o) out=$OPTARG ;;
:) echo "option -$OPTARG needs a value" >&2; exit 1 ;;
\?) echo "unknown option -$OPTARG" >&2; exit 1 ;;
esac
done
opt is : when a value is missing and ? for an unknown letter, and in both cases $OPTARG holds the offending letter. Match the question mark with a backslash, because an unescaped ? is a glob that matches any single character.
getopts handles only single-letter options. Long options such as --verbose need the separate GNU getopt program or a hand-written loop:
while [[ $# -gt 0 ]]; do
case "$1" in
--verbose|-v) verbose=1; shift ;;
--file) file="$2"; shift 2 ;;
--) shift; break ;;
-*) echo "unknown option: $1" >&2; exit 1 ;;
esac
done
Interactive input: read
Scripts that prompt use read:
read -r -p "Your name: " name # -p prints a prompt
read -r -s -p "Password: " secret # -s does not echo what is typed
read -r -t 5 answer # -t gives up after 5 seconds
-r stops backslashes in the input from being treated as escapes, so write it on every read.
Common mistakes
- Forgetting
shift $((OPTIND - 1)). The options are still in"$@"when you loop over the operands. - No branch for
?or:. A typo in a flag is silently ignored. - A value-taking letter without its colon.
"qo"makes-oa flag, and$OPTARGstays empty. - Options after operands.
getoptsstops at the first operand, soscript file -qnever sees-q.
Your exercise
Write the option parser for a small tool. The argument list reaches the script as a line on stdin and is installed for you; your job is the loop and everything after it. The hidden tests cover options in different positions, combined flags, attached values, the -- marker and both kinds of bad usage.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…