Learn how to use the bash
getopts builtin to parse a script options
Operating System and Software Versions
- Operating System: – All Linux distributions
- No special requirements, just access to a bash shell
- # – requires given linux commands to be executed with root privileges either
directly as a root user or by use of
- $ – requires given linux commands to be executed as a regular non-privileged user
Modifying a bash script behavior via runtime options, just like we normally do with command line programs, can be very useful. The bash shell provides the
getopts builtin command, a standard way to achieve this. How to use it is what we will learn in this tutorial.
Creating our tests script
For the purpose of this tutorial, we are going to create a test script, and, with a lot of fantasy, we are going to call it
test.sh. The purpose of this script will be to output something depending on the option we will provide (it won’t be the most useful script in the world but it will be enough to explain how things work).
Let’s open our text editor and begin:
#!/bin/bash set -e set -u set -o pipefail
The provided shell options at the beginning of the scripts are not mandatory, but it’s a good habit to use them in every script we write. In brief,
-e, short for
errexitmodifies the behavior of the shell that will exit whenever a command exits with a non zero status (with some exceptions).
-u is another very important option: this makes the shell to treat undefined variables as errors.
pipefail changes the way commands inside a pipe are evaluated. The exit status of a pipe will be that of the rightmost command to exit with a non zero status, or zero if the all the programs in the pipe are executed successfully. In other words, the pipe will be considered successful if all the commands involved are executed without errors.
The getopts construct
After this brief digression, let’s return to the main point of this tutorial and let’s see how
#!/bin/bash set -e set -u set -o pipefail while getopts 'lha:' OPTION; do case "$OPTION" in l) echo "linuxconfig" ;; h) echo "h stands for h" ;; a) avalue="$OPTARG" echo "The value provided is $OPTARG" ;; ?) echo "script usage: $(basename \$0) [-l] [-h] [-a somevalue]" >&2 exit 1 ;; esac done shift "$(($OPTIND -1))"
Let’s analyze what we have above. First of all the
getopts commands must be used inside a
while loop so all options are parsed. Then immediately after the
getopts keyword, we defined the possible options our script will accept. They are
h and finally
What does the colon after this last option means? It is the way we tell getopts that the option requires an argument. Each parsed option will be stored inside the
$OPTION variable, while an argument, when present, will become the value of the
Each option is managed inside a case statement, with a final
? case that will be matched whenever an option that doesn’t exist will be provided. In that case we will just echo the user the correct script usage and exit with an error code.
Testing our script
First, we are going to call our script providing a non existing option, just to test the case we mentioned above. Let’s give the script executable permissions and then call it:
chmod +x test.sh && ./tesh.sh -c
As expected we will receive a message telling us that the provided option, is illegal and then instructions about how to use the script:
./test.sh: illegal option -- c script usage: test.sh [-l] [-h] [-a somevalue]
script usage string is the one we set to be printed on usage error, the above it, it’s a message automatically generated by
getopts and it can be disabled by prefixing the options with a
:. Now let’s see what happens when we use the script the way it was meant to:
./test.sh -l linuxconfig
We used the
l option, and the script printed onscreen the string we set in the corresponding case, this is also what happens if we provide the
./test.sh -h h stands for h
Let’s now try to call the script with the
a option. As said above this option requires an argument, and will fail if the latter is not provided:
./test.sh -a ./test.sh: option requires an argument -- a script usage: test.sh [-l] [-h] [-a somevalue]
As expected, the script responded with an error message, reminding us that the option we provided requires an argument:
./test.sh -a tux The value provided is tux
This time the script responded without error. Notice how the argument we provided,
tux it’s printed because it becomes the value of the
getopts, you can provide also more than one option at the time to your script, combining the flags when you launch it. For example, let’s see what happens when we try to call our scripts with both the
./test.sh -lh linuxconfig h stands for h
As we can observe, both the options got processed, in the order we provided them. Of curse the options can be given separately, and we can also add the
a option, but always remembering to add the required argument:
./test.sh -l -h -a tux_rulez linuxconfig h stands for h The value provided is tux_rulez
The $OPTIND variable
If we take another look at the very trivial script we have written above, we see another instruction at the end of the while loop:
shift $(($OPTIND -1)). What is its purpose? Let’s say we call the script this way:
./test.sh -l -h -a hello world
When the script is invoked, the
$OPTIND variable is set to 1, and it is incremented each time an option is parsed, until it reaches the last one. Therefore, in this case, it will have a value of 5 when it will reach the last element, which is
hello, the mandatory argument required by the
shift is a shell builtin which moves the positional parameters of the script down a specified number of positions provided to it as a positive number, discarding the related arguments.
The way we launched the script above, the last provided argument, “world”, is not parsed by
getopts, which at that point already finished its job.
Now, if we make a shift of the value of
$OPTIND - 1 (that is the effective number of the parsed options, 4 in this case) on the positional parameters, what remains are just the arguments that are not options, in this case “world”, that we can access by using the
$* variable later in this script.