Bourne's getopts sadly makes simple shell scripts more cluttered and verbose

August 30, 2016

A while back I wrote about how I wanted to use getopts more in my shell scripts in order to have proper real option handling instead of faking it in bad ways. Recently I was modifying a few simple scripts that took arguments, so I decided to do the right thing and switch them from simple hacks to getopts. This worked, but what it showed me is that simple use of getopts is going to make my scripts annoyingly more verbose.

Imagine that I have a script that takes a single option that can change its behavior, say '-f' for 'build Go without also running its native build tests'. In a simple crude script, this is handled like so:

fast=""
if [ "$1" = "-f" ]; then
   fast="y"
fi

This lacks many niceties, but it's short and simple (and in some cases you might just stick the actual extra things to do inside the if condition). The same version with getopts winds up something like this:

usage() { echo "usage: make-all.sh [-f]" 1>&2; exit 1; }
o_fast=
while getopts f opt; do
  case $opt in
    f) o_fast=y;;
    *) usage;;
  esac
done; shift $(($OPTIND-1)); [[ $# != 0 ]] && usage

I've deliberately compacted some lines here in order to make this smaller. One could golf it a bit further, but there are limits to that (both for readability and just in Bourne syntax). And it's still clearly larger (and more complex) than the simple version.

(The actual simple version also uses --fast instead of -f, but getopts doesn't deal with GNU style long options at all; so much for that.)

If I was writing big scripts with complicated argument handling, this wouldn't matter; getopts would be a clear improvement and the code size would be much more comparable. But I have a lot of little scripts that take one or two arguments and do very simple things with command line options, and for those doing things the right way is unavoidably a bunch more verbose.

(Abstracting this into a function that is then in a function library that gets .-included is not a solution because I want my little scripts to be and remain standalone artifacts that I can just copy around freely.)

The result of this is that I wish there was some sort of simple setopts builtin that basically did the simple case; take a set of options, set standard variables for every option present, complain about usage if necessary, and maybe check and complain if you told it how many arguments your command takes. I would use that a bunch, because about 90% of the time that is a great first stage for option and argument handling in my scripts.

(Maybe I need to do a few additional checks for conflicting options if this is an advanced script.)

PS: I'm going to keep the getopts usage in the scripts I've converted and I'm going to try to keep on using it, even in simple scripts. Maybe I'll get acclimatized eventually, and even if I don't it's clearly the right thing to do. Although I really wish getopts could deal with long options, because long options are much better for reminding you what an infrequently used option might do.

Written on 30 August 2016.
« Phones and tablets are going to change what sort of passwords I use
The various IDs of disks, filesystems, software RAID, LVM, et al in Linux »

Page tools: View Source, Add Comment.
Search:
Login: Password:
Atom Syndication: Recent Comments.

Last modified: Tue Aug 30 00:18:59 2016
This dinky wiki is brought to you by the Insane Hackers Guild, Python sub-branch.