On Command Arguments
Table of Contents
1 Introduction
2 Getopts
Getopts, as a tool is another command and a syntax method to identify and collect user needs based on the command arguments. Here are three web-based references, which contain sufficient resources to appreciate its value:
- [bashHackers][]
- [Manual Page][getoptsManpp]
- [Web Search][getoptsSearch]
As a voting member of the Posix committee that approved the command syntax, I supported the adoption of the getopts command. Why? to standardize the command interface. Not that it was ever too fragmented, but simply for standarization. At the risk of too many alternative interfaces, getopts could serve to control an unnecessary proliferation. Rather than:
$ cmmd --whoopee=foobar
getopts would prefer:
$ cmmd -w foobar
Not that this is shorter, but when collected with every flag from a to z, spelled out, the command interface to a function becomes a paragraph and not a statement or clause. The danger, spelled out by Rob Pike, and Brian Kernighan in Usenix '83, appeared in their too-infrequently noted paper: [Unix Style, or cat -v considered harmful][catMinusV]
My current rejection of getopts is not based on its utility, rather its attempt at doing battle with creaping-featurism, when there are better ways to deal with that blight.
A neighboring paper records an [online discussion of getopts](./onGetopts.html) in the summer of '14.
The next two methods are quite similar, and each offers advantages. But, either offers the opportunity of an indefinitely long argument list. My endeavor here is to confine the command interface to no more than two fixed options with an un-limited number of file/directory arguments.
3 The for loop
The for loop allows access to one argument at a time:
for arg in $*; do … $arg …; done
and it shifts each of the positional parameters. The arg variable may be any appropriate name, The case statement is a useful means to handle these arguments:
case $arg in this) handle $arg as "this" ;; that) … esac
To "handle" an arg, one may assign a variable, or note the arg is the suffix of a particular type of file:
case $arg in *.txt) txtfiles="$txtfiles $arg" ;; … esac
which collects the list of txtfiles. There are better ways to deal with this.
I spent a handful of years teaching Tcl to developers at a financial services company. Tcl had been chosen a decade before as the systems/application programming language. I thought it a prudent choice. System architects, through practice, standards, and interface definition had cured the "it's only a scripting language" problem, even leveraging it into the event handler in the .COM, .NET client application interface.
Tcl offered a foreach function. So, I take little credit for this shell function:
foreach () { local cmd=$1; shift; for arg in "$@"; do $cmd $arg; done }
This is used, for example:
$ foreach function *.txt
So, the above function expects it's one and only positional parameter to be an existing TXT file. This feature has greatly reduced the need for for loops in my code. At this moment, out of the 667 functions in my environment, 30 use the foreach function and 23 use a for statement. A common practice is to add the feature to a function designed to use only one argument.
Starting with, say:
dfun () { … use $1 … ; }
modify this to handle multiple variables, at no cost to the existing interface:
dfun () { function _dfun { … use $1 … ; } … foreach _dfun $* }
The foreach function has two companions: foreachi and foreachij, which hold one and two argument, respectively to pass to the underlying function. Here's foreachi:
foreachi () { tracecall $*; local par="$1 $2"; shift 2; for a in "$@"; do $par $a; done }
4 The while loop
The while loop offeres the possibility of dealing with multiple arguments at the same time. An example would be to diff pairs of successive members of a list of files. The liability of this approach is the user must shift the arguments themselves. Here's the diff idea:
while -f $2 ; do
diff $1 $2 > dif/$1 shift done
5 The case statement
My insight in the last few seasons is an un-exploited feature of the case statement. Let's use the number of arguments and the value of any of them together as a single test:
case $#.$1 in 2.edit) … *) … esac
says if there are two arguments, and the first is edit, then do … this, otherwise do that. And if it's real complex, you might start your case statement:
case $#.$1.$2
where your cases may need two "."s in them:
0..)
since, if zero arguments, … etc.