Command line options parser

By Malcolm McLean Homepage

Many UNIX-like commands take commandline options. These can be parsed by hand, but beyond a fairly small level of complexity it becomes a nuisance. Experienced users like short options, however it helps new users if you can also give a long option name.

Most Unix like commands take the format

command [-flags] [-longopt 123] filename1 filename2

The flags are single characters to switch behaviours on and off. However there are also longer options which might take parameters. Finally, most commands can operate on one or more filename.

The interface to an option parser is a difficult blend of usability and functionality, but I think I have got it about right. The goals are, firstly the parser should exist in a single ANSI C source file, secondly it should not modify command line arguments, nor use any globals, thirdly it should report errors in human-readable English language but with the option to suppress (source code is freely available so if your language is not English, simply alter the strings),and lastly each option should be parsed in a single line.

The last requirement means that we shouldn't have to pass the option syntax to the parser in one long, error-prone, complex argument. However it does have the disadvantage that a set of flags might mirror a longer option call, and since we don't have a list of long options there is no easy way of detecting that. So we have to pass in the flags - one character options that take no arguments - at construction time.

The Interface

/* options object constructor Params: argc - number of arguments argv - command line (terminating null pointer) flags - list of legitimate 1 character flags Returns: constructed object Notes: assumes a comandline of the form programname -options -longoption -param 5 filename1 filename2 The first argument can be a plain argument, can be a long option or can be a list of flags introduced with '-'. */ OPTIONS *options(int argc, char **argv, char *flags) The constructor takes local copies of argc and argv, thus you can use the object as you please. If the computer runs out of memory it will return NULL. However all the functions are written to accept null first parameters, so there is no need to check for this. Note that the flags argument should be prefixed with a minus sign, eg "-abc".
/* options object destructor */ void killoptions(OPTIONS *opt) Destroy the object when finished.
/* get an option form the command line Params: opt - the options name - name of option fmt - option arguements format Returns: number of arguments consumed. Notes: name - "-option -o -opt -OPT" - list of alternative names fmt - "%d%32s%f" - list of option parameters %d - an integer - argument int * %f - a real - argument double * %s - a string - argument char * strings should take a buffer length qualifer (default 256) usage if(opt_get(opt, "-f")) f_flagisset(); opt_get("-dimensions -d", "%d%d", &width, &height) Note that there is usually no need to error check. opt_error will report badly-formed parameters. */ int opt_get(OPTIONS *opt, char *name, char *fmt, ...) This is the workhorse function.

It is designed to be as easy as possible to use. Alternative option names should be separated with spaces. So if we have an option that can take the forms "-f", "-fred", and "-Frederick", call with the name parameter "-f -fred -Frederick". Simply load your arguments with defaults, and then call get_opt() with the appropriate scanf()-like format string. So if the "-fred" option takes two integer arguments, call with "%d%d", &x, &y). If the option is a flag that takes no arguments, check the return value to see if it was set or not. If the option requires arguments, generally there is no need to check the return value. If the option is not set, the arguments will be left as default values. If there is an error, it will be picked up on a subsequent call to opt_error().

/* check for errors on the commandline Params: opt - the options object fp - pointer to a stream for error message Returns: 0 if no error, 1 if errors Notes: Caller must parse every option with opt_get()to prevent it being flagged as an unrecognised option. No need to check for null OPTION objects - code is null safe and opt_error returns an out of memory error. */ int opt_error(OPTIONS *opt, FILE *fp) It is important to call this function after you have done with options parsing. Calls to get_opt() are the only way the parser has of knowing which options are legal.If any unrecognised options are left they can be picked up at this point.

Normally the file argument should be stderr. However you can direct error messages to the stream of your choice, or pass NULL. If the opt parameter is null, this will tirgger an out of memory message. Therefore in typical code there is no need to check the return value from the constructor.

/* get the number of arguments Params: opt - the options object Returns: number of commandline arguments that are not options Notes: must call after you have finished option parsing. */ int opt_Nargs(OPTIONS *opt)

This returns the number of non-option arguments. It must be called after all options are consumed. Note that there is potential difficulty if the first non-option argument is allowed to start with a minus sign, because it will be picked up as an unknown option.

/* extract an argument form the command line Params: opt - the options object index - zero-based option index Returns: argument, or NULL on fail */ char *opt_arg(OPTIONS *opt, int index) Gets the non-option arguments by index number. Returns an allocated string, or a quiet zero if the index is out of range.

Typical Usage

Here is an example program to show how to use the system. It doesn't do anything except input some rather silly parameters, and print out message saying how to use it.

#include <stdio.h> #include <stdlib.h> #include "options.h" void usage(void) { printf("Program to test command line parsing\n"); printf("Usage testoptions [options] <mainfile> ...\n"); printf("Options: -a flag for option a\n"); printf(" -b or -flagb flag for option b\n"); printf(" -c flag for option c, means more than one file\n"); printf(" -d <N> - an integer option (default 1) \n"); printf(" -e -ereal <x> - a floating point option (default 1.0)\n"); printf(" -f -fred <name> - a string option (default \"Bloggs\")\n"); printf(" -g -george <name> <N> <x> - an option taking a \n"); printf(" string, an integer, and a real (default \"Porgy\" 2, 3.0)\n"); exit(EXIT_FAILURE); } int main(int argc, char **argv) { OPTIONS *opt; int flaga = 0; int flagb = 0; int flagc = 0; int dval = 1; double ereal = 1.0; char fred[32] = "Bloggs"; char george[32] = "Porgy"; int georgeN = 2; double georgereal = 3.0; char *firstfile; char *filenames[256] = {0}; int i; int Nargs; opt = options(argc, argv, "-abc"); flaga = opt_get(opt, "-a", 0); flagb = opt_get(opt, "-b -flagb", 0); flagc = opt_get(opt, "-c", 0); opt_get(opt, "-d", "%d", &dval); opt_get(opt, "-e -ereal", "%f", &ereal); opt_get(opt, "-f -fred", "%32s", fred); opt_get(opt, "-g - george", "%32s%d%f", george, &georgeN, &georgereal); Nargs = opt_Nargs(opt); if(Nargs < 1 || Nargs > 256) usage(); firstfile = opt_arg(opt, 0); for(i=1; i<Nargs;i++) filenames[i] = opt_arg(opt, i); if(opt_error(opt, stderr)) exit(EXIT_FAILURE); killoptions(opt); if(flagc == 0 && Nargs != 1) usage(); printf("flag a %d\n", flaga); printf("flag b %d\n", flagb); printf("flag c %d\n", flagc); printf("dval %d\n", dval); printf("ereal %f\n", ereal); printf("fred %s\n", fred); printf("george %s %d %f\n", george, georgeN, georgereal); printf("firstfile %s\n", firstfile); for(i=1; i<Nargs;i++) printf("file %d %s\n", i, filenames[i]); free(firstfile); for(i=1;i<Nargs;i++) free(filenames[i]); return 0; }

Source files