getopts: Parsing Command-Line Options in Bash
What You'll Learn
- How to safely parse option arguments like
-aand-b valuein shell scripts - The role and syntax of
getoptsoptstring/OPTARG/OPTIND - How to handle invalid options and missing arguments yourself (silent mode)
- When to use
getopts(shell builtin) vsgetopt(external command)
Quick Summary
- Prefer
getoptsover hand-rolled parsing (POSIX, portable) - The core loop is
while getopts ":a:bc" opt; do ... done - Options that take an argument are declared as
letter:in the optstring (a:) - After the loop, run
shift $((OPTIND - 1))to reach the remaining positional arguments
Assumptions (target environment)
- bash / POSIX sh (
getoptsis a builtin) - Only short options (
-a,-v) are supported. Long options like--verboseare not.
What is getopts?
Conclusion: getopts is a shell builtin for option parsing; paired with a while loop it pulls
-a/-b valuearguments off one at a time.
getopts is a shell builtin that parses command-line options (short - arguments) one at a time. Instead of tearing apart $1 and $2 with a case statement by hand, you get a standardized way to process options.
The basic syntax is:
getopts optstring name [args]
optstring: the set of valid option letters (e.g."abc"means-a-b-c)name: the variable that receives the detected option letter- Each call processes one option, returning exit status 0 (true) while options remain
That "returns true while options remain" behavior is exactly what a while loop needs.
Why use getopts instead of hand parsing?
Conclusion: getopts handles bundled options (
-abc) and the--terminator per the standard, which hand-written case statements tend to miss.
Roll your own while [ $# -gt 0 ] with case "$1" in and you end up reimplementing every subtle rule yourself:
- Bundled options: letting
-a -b -cbe written as-abc - Options with arguments: accepting both
-b valueand-bvalue - The
--terminator: stop treating everything after it as options - Detecting invalid options and missing arguments
getopts handles all of this with POSIX-standard behavior. It is portable and avoids bash-only syntax, so it works unchanged in a #!/bin/sh script.
If long options (--output file) are not a hard requirement, reach for getopts first. The code is shorter and the behavior is standardized.
How do you use getopts?
Conclusion: Declare accepted letters in optstring, append
:to letters that take an argument; OPTARG holds the value and OPTIND the next index.
Writing the optstring
The optstring is a string of accepted option letters. Append : to a letter that takes an argument.
| optstring | Meaning |
|---|---|
"abc" |
-a -b -c (no arguments) |
"a:bc" |
-a takes an argument; -b -c do not |
":a:bc" |
Leading : enables silent mode (below) |
Basic template
#!/bin/bash
verbose=0
output=""
while getopts "vo:" opt; do
case "$opt" in
v) verbose=1 ;;
o) output="$OPTARG" ;;
\?) echo "Unknown option: -$OPTARG" >&2; exit 1 ;;
esac
done
shift $((OPTIND - 1))
echo "verbose=$verbose output=$output"
echo "Remaining arguments: $*"Example run:
$ ./script.sh -v -o result.txt input1 input2
verbose=1 output=result.txt Remaining arguments: input1 input2
OPTARG and OPTIND
getopts updates two variables automatically as it parses.
OPTARG: the argument value of an option that takes one (o:). Above, it isresult.txtfrom-o result.txt.OPTIND: the index of the next argument to process. It starts at1. After the loop it points at the first non-option argument.
Running shift $((OPTIND - 1)) after the loop removes every processed option, leaving only the non-option arguments (file names, etc.) in $1 onward.
OPTIND is initialized to 1 when the shell starts. In a function that runs the getopts loop more than once in the same shell, reset OPTIND=1 before the loop or the second pass will misbehave.
How do you handle errors?
Conclusion: A leading
:in optstring enables silent mode; invalid options put?in name, missing arguments put:, and OPTARG holds the offending letter.
getopts has two error-reporting modes.
Normal mode (optstring does not start with :)
On an invalid option or missing argument, getopts prints a message to standard error itself and stores ? in name. Convenient, but you cannot control the wording.
Silent mode (optstring starts with :)
Start the optstring with a leading : (e.g. ":vo:") and getopts prints no message, letting your script handle every error. The behavior is:
| Situation | Value in name |
Value in OPTARG |
|---|---|---|
| Invalid option | ? |
the offending character |
| Missing argument | : |
the option letter that needed one |
Error handling in silent mode:
while getopts ":vo:" opt; do
case "$opt" in
v) verbose=1 ;;
o) output="$OPTARG" ;;
\?) echo "Unknown option: -$OPTARG" >&2; exit 1 ;;
:) echo "Option -$OPTARG requires an argument" >&2; exit 1 ;;
esac
donePrefer silent mode in practice. You get full control of user-facing output: localize messages, call a usage() helper, and exit cleanly.
How does getopts differ from getopt?
Conclusion: getopts is a builtin for short options only; if you need long options like
--verbose, use the external getopt (util-linux).
They differ by one letter but are different tools.
| Aspect | getopts (builtin) |
getopt (external) |
|---|---|---|
| What it is | Shell builtin | /usr/bin/getopt (util-linux, etc.) |
| Long options | No (-a only) |
Yes (--all) |
| Portability | High, POSIX-standard | Varies (GNU enhanced version may be needed) |
| Usage | Fetch one at a time in a loop | Reorder all args, then parse |
If short options are enough, use getopts; if --output-style long options are a requirement, consider getopt (the GNU enhanced version).
A trick exists to handle long options with getopts alone (treat - as an argument-taking option and re-parse OPTARG), but it hurts readability. When the requirement is firm, plain getopt is easier to maintain.
What are the common pitfalls?
Conclusion: Forgetting to reset OPTIND, forgetting the shift, and passing long options are the classic three.
1. Not resetting OPTIND inside a function
Using getopts in a function carries the previous OPTIND value over. Add local OPTIND (or OPTIND=1) before the loop.
parse_args() {
local OPTIND # function-local, reset each call
while getopts ":vo:" opt; do
# ...
done
}2. Forgetting shift and misaligning positional arguments
Skip shift $((OPTIND - 1)) and $1 stays as an option string, breaking later file handling. Always run it right after the loop.
3. Passing long options
Pass --verbose to getopts and it interprets each character after - (-, v, e...) as a separate option, causing unintended behavior. If you need long options, choose getopt at design time.
Summary
getopts implements shell option parsing in a standardized, portable way as a builtin. Memorize the while getopts ":a:bc" opt; do case ... done form, handle errors yourself in silent mode (leading :), and finish with shift $((OPTIND - 1)) to reach the positional arguments — master these three points and you are ready for real-world scripts.