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
: Theinput
option without an equal sign. The actual argument will be the next item after this option.input=*
: Theinput
option 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
OPTARG
to the option flag for which the argument was expected (e.g.,-f
). - Within the loop, the
opt
variable (or whichever variable you're using in yourgetopts
loop) 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:" opt
specifies that the-f
option 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.
Further Reading
- An Introduction to Linux Command Line for Beginners
- Bash Guide for Beginners
- Advanced Bash-Scripting Guide: Command-line Options
thanks for sharing
in Example 1, it’s missing : at the end of optstring, it should be “:h:” otherwise this case will never be met
To achieve this we would need two arguments, something like:
and then when we execute the script, we will get:
Very helpful, thanks a lot
Thanks a lot for the tutorial, I like it a lot. But I think you go wrong with the colon. At least, when I try your script from example three, I can’t enter a length for the password length. I gives me errors. From other tutorials I tried, I learned that the optionstring should be declared as follows:
instead of:
With this replacement of the colon behind the ‘l’, the ‘l’ parameter expects a value to be entered. Then you can run a command like:
or:
Please correct me if I’m wrong, I’m not an expert (that’s why I followed the tutorial in the first place)
Thanks for highlighting this, I have updated the post and also added some more information regarding the position of the colon. We can add it in the beginning and in the end based on the requirement. Here it makes sense to have it after “l” as we expect an input argument for this param.