This is part 31 of a series on how to approach bash programming in a way that’s safer and more structured than your basic script.
See part 1 if you want to catch the series from the start.
You can read getopt’s man page for full details on its usage. The gist of it is that getopt is a tool which takes your option definitions, much like our existing parser, and ingests the argument list.
Some of the features we’ll get with the enhanced version of getopt include:
short named arguments with no space before the value - e.g. -avalue
combined short flags in one option - e.g. -abc for -a -b -c
combined short flags with a trailing short named argument - e.g. -ab value
long named arguments with an equals instead of space - e.g.
long options given by the minimum prefix necessary to disambiguate them - e.g.
options which come after positional arguments - e.g.
command argument --option
getopt’s output is the same argument list as the input, except that the arguments are reformatted into the form that our existing parser understands. All of the variants for short and long named arguments (space, no space, equals sign, no equals sign) are standardized to a single space. Any invalid options generate an error message and return code.
If you read many blogs or stackoverflow questions on getopt, you’ll see many recommendations to use getopts instead (note the “s” at the end). From the Wikipedia page for getopts, you can see the comparison between versions, as well as the source of some confusion due to the history of the two.
What it all boils down to is that gnu getopt, not getopts, is the best implementation of the three versions. It happens to share its name with the original getopt, which is the worst of the three, hence some confusion.
You’ll see the current version of getopt called enhanced getopt sometimes, and that is the one included in linux by default in the util-linux package.
getopt is an external program, not a shell builtin like getopts, so occasionally folks will make the argument that a builtin is more universal, and perhaps so since MacOS doesn’t have the gnu version, but getopt is significantly better than getopts. Again, use homebrew to make MacOS useful.
In any case, the point of the getopt utility is to handle some of the messier details of the various formats allowed by the specifications. For example, the spec allows the use of any unique prefix of a long option, which means that a long option may be represented by a number of actual strings. Implementing your own logic to determine the smallest unique prefix, and to match any longer version, would be a lot of work for a simple command-line script.
The way to test which version of getopt you have on your system is to run:
If the result code is 4, you have the enhanced version.
We could go about using getopt in two different ways. We could use it directly and pass its output to our parseopts. This would require us to define our options for getopt using its format, then to redefine them for our own parser. Obviously this sounds like more work, but it does give us the ability to denote the difference between required arguments and optional arguments (usually these are named arguments, since required flags don’t make much sense).
The other option is to enhance the parseopts function to take our definition of the options, redefine them itself using getopt’s format so it can call getopt and then do so, all behind the scenes.
I won’t go through the tdd’ing of option 2 at this point, but will instead go straight to the punchline:
If the environment doesn’t have an enhanced getopt, the above code reverts to our existing functionality. If it does, however, it now passes these tests:
Continue with part 32 - parallelism