...making Linux just a little more fun!

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:

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


[BIO]

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.


Copyright © 2009, Bob Hepple. Released under the Open Publication License unless otherwise noted in the body of the article. Linux Gazette is not produced, sponsored, or endorsed by its prior host, SSC, Inc.

Published in Issue 162 of Linux Gazette, May 2009

Tux