10.3. Interpreting program arguments

The loop used to examine the program arguments in the example above is a common C idiom which you will see in many other programs. An additional common idiom is to use ‘options’ to control the behaviour of the program (these are also sometimes called switches or flags). Arguments which start with a ‘-’ are taken to introduce one or more single-letter option indicators, which can be run together or provided separately:

progname -abxu file1 file2
progname -a -b -x -u file1 file2

The idea is that each of the options selects a particular aspect from the program's repertoire of features. An extension to that idea is to allow options to take arguments; if the -x option is specified to take an argument, then this is how it might be used:

progname -x arg file1

so that the arg argument is associated with the option. The options function below automates the processing of this style of use, with the additional (common but preferably considered obsolescent) support for the provision of option arguments immediately following the option letter, as in:

progname -xarg file1

In either of the above cases, the options routine returns the character ‘x’ and sets a global pointer, OptArg, to point to the value arg.

To use this routine, a program must supply a list of valid option letters in the form of a string; when a letter in this string is followed by a ‘:’ this indicates that the option letter is to be followed by an argument. When the program is run, it is then simply a question of repeatedly calling the options routine until no more option letters remain to be found.

It seems to be a fact of life that functions which scan text strings looking for various combinations or patterns within them end up being hard to read; if it's any consolation they aren't all that easy to write either. The code that implements the options is definitely one of the breed, although by no means one of the worst:

/*
* options() parses option letters and option arguments from the argv list.
* Succesive calls return succesive option letters which match one of
* those in the legal list. Option letters may require option arguments
* as indicated by a ':' following the letter in the legal list.
* for example, a legal list of "ab:c" implies that a, b and c are
* all valid options and that b takes an option argument. The option
* argument is passed back to the calling function in the value
* of the global OptArg pointer. The OptIndex gives the next string
* in the argv[] array that has not already been processed by options().
*
* options() returns -1 if there are no more option letters or if
* double SwitchChar is found. Double SwitchChar forces options()
* to finish processing options.
*
* options() returns '?' if an option not in the legal set is
* encountered or an option needing an argument is found without an
* argument following it.
*
*/

#include <stdio.h>
#include <string.h>

static const char SwitchChar = '-';
static const char Unknown = '?';

int OptIndex = 1;       /* first option should be argv[1] */
char *OptArg = NULL;    /* global option argument pointer */

int options(int argc, char *argv[], const char *legal)
{
        static char *posn = "";  /* position in argv[OptIndex] */
        char *legal_index = NULL;
        int letter = 0;

        if(!*posn){
                /* no more args, no SwitchChar or no option letter ? */
                if((OptIndex >= argc) ||
                        (*(posn = argv[OptIndex]) != SwitchChar) ||
                        !*++posn)
                                return -1;
                /* find double SwitchChar ? */
                if(*posn == SwitchChar){
                        OptIndex++;
                        return -1;
                }
        }
        letter = *posn++;
        if(!(legal_index = strchr(legal, letter))){
                if(!*posn)
                        OptIndex++;
                return Unknown;
        }
        if(*++legal_index != ':'){
                /* no option argument */
                OptArg = NULL;
                if(!*posn)
                        OptIndex++;
        } else {
                if(*posn)
                        /* no space between opt and opt arg */
                        OptArg = posn;
                else
                        if(argc <= ++OptIndex){
                                posn = "";
                                return Unknown;
                        } else
                                OptArg = argv[OptIndex];
                posn = "";
                OptIndex++;
        }
        return letter;
}
Example 10.2