bash getopts is a built-in command in Bash that facilitates the
parsing of command-line options and arguments in a standardized way.
With bash getopts, script developers can define expected options for
their scripts and retrieve the values passed with these options,
ensuring a more user-friendly and robust script experience. Whether
you’re building a small utility script or a complex pipeline tool,
bash getopts equips you with the functionality to handle user inputs
efficiently and effectively. This tutorial will delve deep into how you
can harness the power of bash getopts to enhance your Bash scripts and
provide a seamless user experience.
Getting started with bash getopts
Navigating the world of Bash scripting requires a grasp of various tools
and techniques that can enhance script interactivity and usability. One
such crucial tool is bash getopts, which offers a structured approach
to handle command-line options and arguments.
Understanding the Syntax
At its core, bash getopts operates using a specific syntax that allows
it to define and recognize valid options. The basic format is:
getopts OPTSTRING VARNAME. Here, the OPTSTRING defines the options
your script expects, with a colon : indicating if an option expects an
argument. The VARNAME is a variable that will be populated with the
currently processed option.
For instance, if a script expects options -a and -b with -b
requiring an argument, the OPTSTRING would be “a:b”. As bash getopts
processes options, it fills the VARNAME with the current option, making
it easier for the script to take relevant actions based on user input.
Difference between getopts and traditional positional parameters
Traditional positional parameters in Bash, like $1, $2, etc., represent arguments passed to the script in the order they appear. They’re straightforward but can become cumbersome and error-prone when scripts expect numerous or optional arguments.
On the contrary, bash getopts provides a more intuitive and flexible
way of capturing and processing command-line options. Instead of relying
on the strict order of arguments, bash getopts allows users to pass
options in any sequence. This flexibility ensures that users can
interact with scripts more naturally, without needing to remember the
precise order of arguments.
Moreover, bash getopts can handle options that require values and
ensures that these values are correctly associated with their respective
options. This distinction eliminates the ambiguity that might arise with
traditional positional parameters, especially when some arguments are
omitted.
The Role of Colons in bash getopts
The colon : plays an essential role in the bash getopts command, and
it determines how specific options are treated. Understanding its usage
is crucial for writing robust and user-friendly shell scripts.
1. Colon at the Start: Silent Mode
Placing a colon at the beginning of the option string enables “silent
mode.” In this mode, getopts will not output error messages for
unrecognized options or missing option arguments. Instead, the ?
character will be used to signify errors, allowing the script to handle
errors more gracefully.
while getopts ":a:" opt; do
...
done
In the above example, because of the leading colon, if an unrecognized
option is passed, the case for \? will be triggered.
2. Colon After an Option: Expecting an Argument
If a colon follows an option letter, it means that option expects an
argument. If the option is used without supplying its expected argument,
the case for ? will be triggered.
while getopts "a:b:" opt; do
...
done
Here, both -a and -b expect arguments. If either is used without an
accompanying argument, it will be an error.
3. No Colon: Option Without an Argument
Options without following colons don’t expect arguments. They act as flags, simply signifying whether they’ve been passed or not.
while getopts "ab" opt; do
...
done
In this scenario, -a and -b are flags. They don’t need any
accompanying arguments, and the script will just check if they are set.
Declaring Short Options with bash getopts
One of the most striking features of bash getopts is its capability to
facilitate the declaration of short options. Short options are usually a
single character prefixed by a hyphen. They offer a concise way to
specify command-line flags, ensuring scripts remain user-friendly and
intuitive.
1. Single character options (e.g., -a)
With bash getopts, specifying a single character option is
straightforward. Let’s take the example of a script that expects an
option -a:
while getopts "a" opt; do
case $opt in
a)
echo "Option -a triggered"
;;
\?)
echo "Invalid option: -$OPTARG"
;;
esac
done
When this script is executed with -a, it will print “Option -a
triggered”.
2. Handling options with associated arguments (e.g., -f filename)
Sometimes, options need values. For instance, a file option -f might
require a filename. bash getopts gracefully handles such scenarios:
while getopts "f:" opt; do
case $opt in
f)
echo "Option -f triggered with argument '$OPTARG'"
;;
\?)
echo "Invalid option: -$OPTARG"
;;
:)
echo "Option -$OPTARG requires an argument."
;;
esac
done
In the above script, the f: in the getopts string indicates that
-f expects an argument. The $OPTARG variable then provides access to
the argument’s value. If you run the script with -f filename.txt, it
will output “Option -f triggered with argument ‘filename.txt’”.
Here are some sample execution commands
$ bash script_name.sh -f filename.txt
Option -f triggered with argument 'filename.txt'
$ bash script_name.sh -f
Option -f requires an argument.
Using OPTARG to Retrieve Option Arguments with bash getopts
In bash scripting, parsing options and their respective arguments can be
a bit tricky, but bash getopts simplifies this task. When a user wants
to pass an argument to a script via the command line, they use options
(like -f). The OPTARG variable is an inherent part of the getopts
utility in bash. It is utilized to read the argument value for the
options that require one.
How OPTARG works:
With bash getopts, when an option is followed by a colon (like f:),
it indicates that this option requires an argument. Whenever such an
option is found, getopts automatically assigns the following value to
the OPTARG variable. So, if you have a script that expects -f to
have an argument, you can retrieve this argument’s value using
$OPTARG.
#!/bin/bash
while getopts ":f:" opt; do
case $opt in
f)
echo "Option -f has argument '$OPTARG'"
;;
\?)
echo "Invalid option: -$OPTARG"
;;
:)
echo "Option -$OPTARG requires an argument."
;;
esac
done
Handling unexpected option arguments:
One common issue while working with bash getopts is the possibility of
an option getting an unexpected argument or missing its expected
argument. The utility provides error handling for such cases. The 🙂 pattern
matches any option that expects an argument but hasn’t received one,
allowing for easy error reporting.
$ sh script.sh -f filename.txt
Option -f has argument 'filename.txt'
$ sh script.sh -f
Option -f requires an argument.
Handling Invalid Options with bash getopts
While writing bash scripts, it’s crucial to provide meaningful feedback
to the user, especially when they input something unexpected or
incorrect. bash getopts facilitates this by offering an easy mechanism
to detect and handle invalid options.
Using the ? character:
In the context of bash getopts, the ? character plays a special
role. If the user provides an option that hasn’t been declared in the
option string of getopts, the ? character will match it. This gives
script developers the power to notify users of their mistake.
#!/bin/bash
while getopts ":a:b:" opt; do
case $opt in
a)
echo "Option -a has argument '$OPTARG'"
;;
b)
echo "Option -b has argument '$OPTARG'"
;;
\?)
echo "Invalid option: -$OPTARG"
exit 1
;;
:)
echo "Option -$OPTARG requires an argument."
exit 1
;;
esac
done
Customizing error messages for unrecognized options:
By default, bash getopts sends its error messages to standard error.
However, you might want to capture these errors and display a custom
message. You can suppress the default behavior and customize the error
messages for a more user-friendly experience.
Example:
To suppress default messages, use getopts like this:
getopts ":a:b:" opt 2>/dev/null.
Now, incorporating that into our script:
#!/bin/bash
while getopts ":a:b:" opt 2>/dev/null; do
case $opt in
a)
echo "Option -a has argument '$OPTARG'"
;;
b)
echo "Option -b has argument '$OPTARG'"
;;
\?)
echo "Oops! -$OPTARG isn't a valid option for this script."
exit 1
;;
:)
echo "Oops! Option -$OPTARG requires an argument."
exit 1
;;
esac
done
Supporting Long Options with getopts
While bash getopts is primarily designed for parsing short options,
with a little creativity, we can also utilize it for handling long
options. This section provides an advanced technique to parse long
options like --input or --output=file.txt.
The trick involves using a special placeholder - in the options
specification string. When getopts encounters --, it treats
everything that follows as an argument to the - option. This enables
the differentiation between short and long options.
Suppose you have a script that accepts both short and long options to specify input, output, and a verbosity level.
#!/usr/bin/env bash
optspec=":io:v-:"
while getopts "$optspec" optchar; do
case "${optchar}" in
-)
case "${OPTARG}" in
input)
input_file="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 ))
echo "Specified input file with '--${OPTARG}': ${input_file}" >&2;
;;
input=*)
input_file=${OPTARG#*=}
echo "Specified input file with '--input=${input_file}'" >&2;
;;
output)
output_file="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 ))
echo "Specified output file with '--${OPTARG}': ${output_file}" >&2;
;;
output=*)
output_file=${OPTARG#*=}
echo "Specified output file with '--output=${output_file}'" >&2;
;;
*)
if [ "$OPTERR" = 1 ] && [ "${optspec:0:1}" != ":" ]; then
echo "Unknown option --${OPTARG}" >&2
fi
;;
esac;;
i)
echo "Specified input file with '-i': ${OPTARG}" >&2;
;;
o)
echo "Specified output file with '-o': ${OPTARG}" >&2;
;;
v)
echo "Verbose mode activated with '-v'" >&2;
;;
*)
if [ "$OPTERR" != 1 ] || [ "${optspec:0:1}" = ":" ]; then
echo "Non-option argument: '-${OPTARG}'" >&2
fi
;;
esac
done
Let us break down the script:
optspec=":io:v-:"
This string defines the options our script accepts:
i: The input option without an argument.o:: The output option expecting an argument.v: The verbosity option without an argument.-:: This is the trick. The-allows us to handle long options by treating them as arguments to this-option.
while getopts "$optspec" optchar; do
...
done
This loop iterates over each option provided when the script is run. For
each option, it sets the variable optchar to the option’s character.
Inside the loop, a case statement is used to handle each option:
case "${optchar}" in
...
esac
Each option is checked in turn, and actions are taken based on the option.
For long options, the trick is to check if the optchar is -:
-)
case "${OPTARG}" in
...
esac;;
When getopts encounters a --option, it treats the --option as an
argument to the - option. This is how long options are handled.
Inside this nested case statement, we check for specific long options:
input: Theinputoption without an equal sign. The actual argument will be the next item after this option.input=*: Theinputoption with an equal sign. The actual argument is everything after the equal sign.
Short options are handled in the main case statement. For example:
i)
echo "Specified input file with '-i': ${OPTARG}" >&2;;
This part handles the -i option. The OPTARG variable contains the
argument provided to an option (if any).
The script also contains mechanisms to handle invalid options or missing arguments:
*)
if [ "$OPTERR" != 1 ] || [ "${optspec:0:1}" = ":" ]; then
echo "Non-option argument: '-${OPTARG}'" >&2
fi
;;
This portion will print an error if an unrecognized option is given or if an expected argument is missing.
Usage:
$ ./script.sh --input=file.txt --output=out.txt
Specified input file with '--input': file.txt
Specified output file with '--output': out.txt
$ ./script.sh -i file.txt -o out.txt
Specified input file with '-i': file.txt
Specified output file with '-o': out.txt
$ ./script.sh --input=file.txt -v
Specified input file with '--input': file.txt
Verbose mode activated with '-v'
Loop Termination with -- in bash getopts
In the realm of command-line processing, the double dash -- is a
standard convention used to indicate the end of command options. After
the --, all the arguments are treated as filenames and not as options.
This is especially useful when dealing with filenames or arguments that
might be confused with command-line options. With bash getopts, this
convention is also adhered to.
Significance of the double dash in bash getopts: The -- signal is
a sentinel that tells the getopts utility to stop processing options.
This can be particularly handy when arguments might be misinterpreted as
options.
Consider a script that moves
files to a directory. Some of these filenames might start with a
-, which can be mistaken for an option.
#!/bin/bash
dest_dir=""
while getopts ":d:" opt; do
case $opt in
d)
dest_dir=$OPTARG
;;
\?)
echo "Invalid option: -$OPTARG" >&2
;;
esac
done
# Shift past the last option parsed by getopts
shift $((OPTIND-1))
# Any arguments after -- will be treated as filenames, even if they start with -
for file in "$@"; do
mv "$file" "$dest_dir"
done
In the above example, files named -file1.txt and -file2.txt are
created. The -- in the touch command prevents these filenames from
being interpreted as options. Similarly, when invoking our script, we
use -- to ensure that these files are treated as arguments and not
options by bash getopts.
Usage:
$ touch -- -file1.txt -file2.txt
$ mkdir dest_folder
$ sh script.sh -d dest_folder -- -file1.txt -file2.txt
Once getopts encounters the --, it stops processing options, and the
rest of the arguments can be accessed using the positional parameters.
The shift $((OPTIND-1)) command is used to discard the options that
have been parsed, leaving only the non-option arguments in the position
parameters, which can then be easily iterated over or processed.
How to declare Mandatory Arguments
In bash scripting, it’s often necessary to specify command-line
arguments that are either mandatory or optional. bash getopts provides
a built-in way to handle such scenarios efficiently. Let’s dive into how
you can set up both mandatory and optional arguments using
bash getopts.
When using getopts, the way to indicate that an option requires an
argument is by following the option letter with a colon in the getopts
specification string. For example, :f: would indicate that the -f
option requires an argument.
However, when this mandatory argument is missing during the script’s
invocation, getopts doesn’t halt the script by default. Instead, it
will:
- Set the variable
OPTARGto the option flag for which the argument was expected (e.g.,-f). - Within the loop, the
optvariable (or whichever variable you’re using in yourgetoptsloop) will take the value:.
Consider a script that requires a file name with the -f option.
#!/bin/bash
# Specify that -f option requires an argument
while getopts ":f:" opt; do
case $opt in
f)
filename=$OPTARG
;;
\?)
echo "Invalid option: -$OPTARG" >&2
exit 1
;;
:)
echo "Option -$OPTARG requires an argument." >&2
exit 1
;;
esac
done
# Continue script execution
if [ -z "$filename" ]; then
echo "File name not provided."
else
echo "File name provided: $filename"
fi
How this works:
getopts ":f:" optspecifies that the-foption requires an argument.- In the case statement:
\?)handles invalid options.:)handles the scenario where an expected argument is missing.- If the
:case is triggered (indicating a missing mandatory argument for-f), an error message is printed, and the script exits.
How to declare Optional Arguments
getopts doesn’t directly support optional arguments in the traditional
sense. One common way to handle this situation is to check the next
argument. If it’s another option (starts with -), or if there are no
more arguments, then you use the default value.
#!/bin/bash
file_name="default_file.txt" # default value
while getopts ":f:" opt; do
case $opt in
f)
# Check if the next argument exists and does not start with a '-'
if [ -n "$OPTARG" ] && [[ $OPTARG != -* ]]; then
file_name="$OPTARG"
else
# If the next argument is another option or doesn't exist, reset the index so that getopts processes it correctly
OPTIND=$((OPTIND - 1))
fi
;;
\?)
echo "Invalid option: -$OPTARG" >&2
exit 1
;;
:)
echo "Using default value for -$OPTARG."
;;
esac
done
echo "File name provided: $file_name"
Sample Output:
$ ./script.sh -f
Using default value for -f.
File name provided: default_file.txt
$ ./script.sh -f somefile.txt
File name provided: somefile.txt
$ ./script.sh -f -a
Using default value for -f.
Invalid option: -a
In this script, the logic checks if $OPTARG (the argument to the -f
option) is non-empty and doesn’t start with a -. If it starts with
-, it decrements the OPTIND index to ensure the next loop iteration
considers this next argument.
How to declare arguments without any value
If you want an option that does not take any argument (i.e., a flag),
you simply declare it in the getopts specification string without a
following colon.
Here’s an example script that demonstrates how to handle options that
don’t require arguments using bash getopts:
#!/bin/bash
verbose=false
backup=false
while getopts ":vb" opt; do
case $opt in
v)
verbose=true
;;
b)
backup=true
;;
\?)
echo "Invalid option: -$OPTARG" >&2
exit 1
;;
esac
done
# Continue script execution
if [ "$verbose" = true ]; then
echo "Verbose mode is ON."
else
echo "Verbose mode is OFF."
fi
if [ "$backup" = true ]; then
echo "Backup option is selected."
else
echo "Backup option is not selected."
fi
In this example, -v and -b are options that do not take any
arguments. They act as flags to modify the behavior of the script.
Sample Usage:
$ ./script.sh
Verbose mode is OFF.
Backup option is not selected.
$ ./script.sh -v
Verbose mode is ON.
Backup option is not selected.
$ ./script.sh -b
Verbose mode is OFF.
Backup option is selected.
$ ./script.sh -v -b
Verbose mode is ON.
Backup option is selected.
Enforce positional order for input arguments
Assuming we have a scenario wherein we want to enforce a specific order in which the arguments are passed i.e. -a must be passed before -b for the script.
#!/bin/bash
a_declared=false
b_declared=false
while getopts ":ab" opt; do # Notice that the colon after b is removed.
case $opt in
a)
a_declared=true
;;
b)
if [ "$a_declared" = false ]; then
echo "Error: -a must be declared before -b" >&2
exit 1
else
b_declared=true
fi
;;
\?)
echo "Invalid option: -$OPTARG" >&2
exit 1
;;
esac
done
if [ "$a_declared" = true ]; then
echo "Option -a is declared."
fi
if [ "$b_declared" = true ]; then
echo "Option -b is declared."
fi
Usage:
./script.sh -a -b
Option -a is declared.
Option -b is declared.
$ ./script.sh -b -a
Error: -a must be declared before -b
Advanced Techniques
As you dive deeper into the world of bash scripting, you’ll encounter
scenarios where the basic usage of bash getopts may not suffice. In
this section, we’re going to explore some of these advanced techniques,
enhancing your scripting capabilities.
1. Using getopts in Functions
Utilizing getopts within functions can make your script modular and
more readable. It allows for option parsing at multiple points in your
script, especially if it’s complex.
function process_files() {
local optspec=":i:o:"
while getopts "$optspec" optchar; do
case "${optchar}" in
i)
echo "Function received input file with '-i': ${OPTARG}"
;;
o)
echo "Function received output file with '-o': ${OPTARG}"
;;
esac
done
shift $((OPTIND-1))
echo "Non-option arguments in function: $@"
}
# Usage
process_files -i input.txt -o output.txt extra_arg1 extra_arg2
In the example, the bash getopts utility is used within the
process_files function. This keeps option parsing localized and
provides clarity on how options relate to the function’s functionality.
2. Handling Options that Can Appear Multiple Times
Sometimes, you might want to allow an option to be specified multiple times. For instance, if you’re adding multiple files or flags.
files=()
while getopts ":a:" opt; do
case $opt in
a)
files+=("${OPTARG}")
;;
\?)
echo "Invalid option: -$OPTARG" >&2
;;
esac
done
echo "Added files are: ${files[@]}"
When you run the script with ./script.sh -a file1.txt -a file2.txt,
the output will be:
Added files are: file1.txt file2.txt
By using an array (files in the example), we can capture all instances
of an option. This technique is particularly useful when you want to
allow users to specify an option multiple times.
Alternatives to bash getopts
While bash getopts provides a robust mechanism for parsing short
options in bash scripts, there might be scenarios where you seek a more
versatile solution or just want to employ a different approach. Let’s
explore some alternatives to getopts.
1. Introduction to shift and case
Using a combination of shift and case allows for a straightforward
way to parse command-line arguments in bash scripts. The shift command
shifts the positional parameters to the left, making it easier to
process each argument sequentially.
while [ "$#" -gt 0 ]; do
case "$1" in
-f|--file)
file="$2"
shift 2
;;
-d|--directory)
dir="$2"
shift 2
;;
*)
echo "Unknown option: $1"
shift 1
;;
esac
done
echo "File: $file, Directory: $dir"
This script can handle both short (-f, -d) and long (--file,
--directory) options. The shift command is used to move to the next
argument.
2. Third-party Tools: argbash
argbash is a code generator, producing bash scripts that parse their arguments. With argbash, you define the script’s interface, and argbash produces the corresponding parsing code for you.
First, you define your script’s interface:
# ARG_OPTIONAL_SINGLE([file],[f],[Specify a file])
# ARG_OPTIONAL_SINGLE([directory],[d],[Specify a directory])
# ARG_HELP([The script description])
# ARGBASH_GO
After running this through argbash (argbash myscript.m4 -o myscript), it generates a myscript bash script with the argument parsing code included.
Usage:
./myscript --file myfile.txt --directory mydir/
While bash getopts is built into bash and doesn’t require any external
dependencies, sometimes, for more intricate scenarios or personal
preferences, alternatives like shift with case or third-party tools
such as argbash might be more fitting.
Frequently Asked Questions
What is bash getopts used for?
bash getopts is a built-in command in bash that facilitates the
parsing of command-line options and arguments in scripts. It simplifies
the process of handling and verifying options passed to a script.
How does bash getopts differ from getopt?
While both are used for parsing command-line arguments, bash getopts
is a built-in command in bash, ideal for short options. On the other
hand, getopt is an external utility that can handle both short and
long options but might not be available on all systems.
How do I handle long options with bash getopts?
By default, bash getopts is designed for short options. For long
options, you can use advanced techniques involving case statements and
OPTARG, or consider using third-party tools like argbash.
Can I specify mandatory arguments for options using bash getopts?
Yes. When defining an option, follow the option letter with a colon.
This indicates that the option expects an argument. For example, in
getopts "f:", the -f option expects an argument.
How can I display a help message using bash getopts?
You can utilize the ? character in your option string. When an
unrecognized option is encountered, control is passed to the ? case in
your script, where you can display a help message.
How do I handle options that can appear multiple times?
You can use arrays to store values each time an option appears. Each time the option is encountered, append the argument to the array.
What does OPTIND represent in bash getopts?
OPTIND holds the index of the next argument to be processed. It can be
used to process non-option arguments after all options have been parsed.
Why use shift "$((OPTIND-1))" after processing options?
After processing all options, OPTIND points to the next argument
(which could be a non-option argument). Using shift "$((OPTIND-1))"
repositions the positional parameters, so $1 refers to the first
non-option argument.
Can bash getopts handle options with optional arguments?
Not directly. However, you can use advanced techniques to check if the next argument is another option or an actual argument for the current option.
Are there limitations to bash getopts?
While bash getopts is powerful, it primarily handles short options.
For scripts requiring extensive command-line interfaces or support for
long options, consider alternative methods or third-party tools.
Summary
Bash getopts serves as a powerful utility to enhance the parsing of command-line options in bash scripts. This comprehensive guide on “bash getopts” illuminates its pivotal role in making shell scripts more robust and user-friendly. Covering the intricacies of option handling, from basic flag checks to more advanced techniques like supporting long options, this guide ensures script writers can take full advantage of getopts. Whether you’re a beginner trying to decipher the importance of colons in getopts or a seasoned developer looking to handle complex scenarios, this article offers insights that cater to all levels of expertise.


