Command-Line Processing with 'process-getopt'
By Bob Hepple
You know you oughta...
... make your bash(1) scripts as pretty as your C code, but somehow there's
never time. If it's just for your own use, then maybe you don't care -
until you forget what foobar
does, and you try foobar
-h
. Uh oh, yes, now I remember: -h
stands for "halt".
Whoops.
This article explains a simple way to spruce up your scripts so that they act predictably in the Unix way concerning options and arguments. But first of all, a little discussion of the problems the bash(1) developer faces.
When it does matter, when other people are going to be using your script, or when you can't rely on your own memory (don't worry, it'll happen to you too one day), it's a pain to code responses in bash(1) to all the possible inputs that people can make. Other people can be pretty inventive in breaking things with syntax you never imagined.
Then there's the matter of standards: Did you even know that there's a GNU standard for this kind of stuff?
For example, does your script support long options (--verbose
)
as well as short ones (-V
)? Long options are great for making
scripts self-documenting, short ones for power users (and those of us still
typing on glass teletypes).
Is your script flexible about spacing around option arguments? e.g.,
-n 123
should be equivalent to -n123
and to
--number 123
and --number=123
.
Can long options be given as the smallest unique substring, e.g.,
--numb
as a shorthand for --number
?
Can you fold short options together, e.g., -t -s -u
as
-tsu
?
Does -h, --help
consistently display help (to stdout, thank
you very much, so that the user can pipe it into a pager program like
less(1))?
How about -v,--verbose
or -V, --version
? ... and
so on. These are all things that users (OK, Unix-savvy users) expect - so
adhering to these conventions makes everyone's life easier.
But it's really quite tedious and surprisingly hard to get all this right when you write your own bash(1) code, and it's not surprising that so few scripts end up being user-hardened.
Where it really hurts is when you try to maintain that bash(1) code. Even with getopt(1) this can be a pain (actually the syntax for getopt(1) itself is pretty arcane), because the getopt(1) command structure requires you to repeat the option letters and strings in three places:
- the call to getopt(1)
- the case statement to process the options
- the help and man(1) pages.
This repetition introduces wide margins for bugs to creep into the code.
A solution
In compiled languages, there are wrapper functions to put around getopt(3) that help considerably in reducing the labour and opportunity for error, in writing this sort of code. They include GNU's own argp(3) and Red Hat's popt(3).
In python(1) you can use OptionParse.
For bash(1) scripts, there hasn't been anything since getoptx(1) died out - but, in all my own scripts for the past few years, I've been using a library that I wrote: process-getopt(1). It's a wrapper around getopt(1) that makes life considerably easier for bash(1) scripters, maintainers, and users.
As an example, here's a tiny script that uses it to process its command line:
#!/bin/bash PROG=$(basename $0) VERSION="1" USAGE="A tiny example" # call process-getopt functions to define some options: source ./process-getopt SLOT_func() { [ "$1" ] && SLOT="yes"; } # callback for SLOT option add_opt SLOT "boolean option" s "" slot TOKEN_func() { [ "$1" ] && TOKEN="$2"; } # callback for TOKEN option add_opt TOKEN "this option takes a value" t n token number add_std_opts # define the standard options --help etc: # The next 4 lines call the callbacks and remove the options from the command line: TEMP=$(call_getopt "$@") || exit 1 eval set -- "$TEMP" process_opts "$@" shift "$?" # remove the options from the command line # The hard lifting is done - $@ just contains our arguments: echo "SLOT=$SLOT" echo "TOKEN=$TOKEN" echo "args=$@"
... and you're done. Here's the sort of output you get without any further coding:
$ tiny --help Usage: tiny [-shVvq-] [--slot --help --version --verbose --quiet] [-t,--token= ] A tiny example Options: -s, --slot boolean option -t n, --token=number this option takes a value -h, --help print this help and exit -V, --version print version and exit -v, --verbose do it verbosely -q, --quiet do it quietly (negates -v) -- explicitly ends the options
Here's an example of using the options and arguments on the command line:
$ tiny -s --token="my token" arg1 arg2 SLOT=yes TOKEN=my token args=arg1 arg2
process-getopt(1) works with bash-2.04 and later and lives at:
https://sourceforge.net/projects/process-getopt
It's pretty easy to convert your existing scripts to use process-getopt(1): Follow the samples and manuals here:
https://bhepple.freeshell.org/oddmuse/wiki.cgi/process-getopt
Here's a direct link to the manual:
https://bhepple.freeshell.org/scripts/process-getopt.pdf
Enjoy!
Talkback: Discuss this article with The Answer Gang
Bob Hepple is the youngest grumpy old man at Promptu Corp on the Gold Coast in Australia and earned his UNIX stripes at Hewlett-Packard in 1981. Since then he's worked in Asia and Australia for various UNIX vendors and crypto companies - but always in UNIX and GNU/Linux.
Originally a Geordie land-surveyor, he now thinks he's dinky-di after 30 years of Oz - but apparently the pommie accent gives the game away.