ObjectiveLearn how to use the bash
getoptsbuiltin 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 command to be executed with root privileges either directly as a root user or by use of
- $ - given command to be executed as a regular non-privileged user
IntroductionModifying 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
getoptsbuiltin command, a standard way to achieve this. How to use it is what we will learn in this tutorial.
Creating our tests scriptFor 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 pipefailThe 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).
-uis another very important option: this makes the shell to treat undefined variables as errors.
pipefailchanges 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 constructAfter 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
getoptscommands must be used inside a
whileloop so all options are parsed. Then immediately after the
getoptskeyword, we defined the possible options our script will accept. They are
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
$OPTIONvariable, 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 scriptFirst, 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 -cAs 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]While the
script usagestring is the one we set to be printed on usage error, the above it, it's a message automatically generated by
getoptsand 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 linuxconfigWe used the
loption, 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 hLet's now try to call the script with the
aoption. 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 tuxThis time the script responded without error. Notice how the argument we provided,
tuxit'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 hAs 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
aoption, 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 variableIf 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 worldWhen the script is invoked, the
$OPTINDvariable 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
shiftis 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.