In an optstring, the colon has an extra meaning beyond turning off verbose error logging: when placed after a letter, it signals to getopts that an option argument is expected.
If we look back at our first example, the optstring was simply :v. If we wanted the -v flag to accept an argument, we would place a colon behind the v, which would result in the following optstring: :v:. We can then use a special variable we've seen before, OPTARG, to grab that option argument.
We'll make a revision to our single-flag.sh script to show you how this works:
reader@ubuntu:~/scripts/chapter_15$ vim single-flag.sh
reader@ubuntu:~/scripts/chapter_15$ cat single-flag.sh
#!/bin/bash
#####################################
# Author: Sebastiaan Tammer
# Version: v1.1.0
# Date: 2018-12-14
# Description: Shows the basic getopts syntax.
# Usage: ./single-flag.sh [flags]
#####################################
# Parse the flags in a while loop.
# After the last flag, getopts returns false which ends the loop.
optstring=":v:"
while getopts ${optstring} options; do
case ${options} in
v)
echo "-v was found!"
echo "-v option argument is: ${OPTARG}."
;;
?)
echo "Invalid option: -${OPTARG}."
exit 1
;;
esac
done
The changed lines have been highlighted for your convenience. By adding a colon to the optstring and using the OPTARG variable in the v) block, we now see the following behavior when running the script:
reader@ubuntu:~/scripts/chapter_15$ bash single-flag.sh
reader@ubuntu:~/scripts/chapter_15$ bash single-flag.sh -v Hello
-v was found!
-v option argument is: Hello.
reader@ubuntu:~/scripts/chapter_15$ bash single-flag.sh -vHello
-v was found!
-v option argument is: Hello.
As you can see, as long as we supply the flag and flag argument, our script works just fine. We do not even need a space between the flag and flag argument; since getopts knows an argument is expected, it can handle either a space or no space. We'd always recommend including the space in any case, to ensure readability, but it is not technically needed.
Also, this proves why we need a separate optstring: the case statement is the same, but getopts now expects an argument, something we could not have done if the creators had omitted the optstring.
As with all things that seem too good to be true, this is one of those situations. While it works fine if the user is nice to your script, if he/she is not, the following might happen:
reader@ubuntu:~/scripts/chapter_15$ bash single-flag.sh -v
Invalid option: -v.
reader@ubuntu:~/scripts/chapter_15$ bash single-flag.sh -v ''
-v was found!
-v option argument is:
Now that we've told getopts to expect an argument to the -v flag, it will actually not correctly identify the flag if there is no argument. An empty argument, as denoted by the '' in the second script call, is fine, however. (Technically fine, that is, since no user would ever do that.)
Fortunately, there is a solution for this—the :) block, as follows:
reader@ubuntu:~/scripts/chapter_15$ vim single-flag.sh
reader@ubuntu:~/scripts/chapter_15$ cat single-flag.sh
#!/bin/bash
#####################################
# Author: Sebastiaan Tammer
# Version: v1.2.0
# Date: 2018-12-14
# Description: Shows the basic getopts syntax.
# Usage: ./single-flag.sh [flags]
#####################################
# Parse the flags in a while loop.
# After the last flag, getopts returns false which ends the loop.
optstring=":v:"
while getopts ${optstring} options; do
case ${options} in
v)
echo "-v was found!"
echo "-v option argument is: ${OPTARG}."
;;
:)
echo "-${OPTARG} requires an argument."
exit 1
;;
?)
echo "Invalid option: -${OPTARG}."
exit 1
;;
esac
done
It might be a little confusing that both a wrong flag and a missing option argument are resolved as the OPTARG. Without making this situation more complicated than it has to be, it all depends on whether the case statement block contains ?) or :) at that moment. For ?) blocks, everything that is not recognized (the whole flag) is seen as the option argument, and :) blocks only trigger when the optstring contains the proper instruction for an option with an argument.
Everything should work just as intended now:
reader@ubuntu:~/scripts/chapter_15$ bash single-flag.sh
reader@ubuntu:~/scripts/chapter_15$ bash single-flag.sh -v
-v requires an argument.
reader@ubuntu:~/scripts/chapter_15$ bash single-flag.sh -v Hi
-v was found!
-v option argument is: Hi.
reader@ubuntu:~/scripts/chapter_15$ bash single-flag.sh -x Hi
Invalid option: -x.
reader@ubuntu:~/scripts/chapter_15$ bash single-flag.sh -x -v Hi
Invalid option: -x.
Again, because of the sequential processing of flags, the final call never gets to the -v flag due to the exit 1 in the ?) block. However, all other situations are now properly resolved. Nice!