Stewart Heitmann
August 1999
The argtable library and accompanying documentation is copyright © 1998, 1999 Stewart Heitmann (Stewart.Heitmann@tip.csiro.au)
Argtable is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.
This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU Library General Public License for more details.
You should have received a copy of the GNU Library General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
Argtable is a freely available programmer's library for parsing
the command line arguments of any C/C++ program. It allows the
programmer to specify the desired format of the command line
arguments in one or more statically defined arrays known as argument
tables. Each row of an argument table specifies the data type of
an expected argument and nominates a user-defined program variable as
storage for the incoming argument value. If arguments require default
values, then these too are specified in the argument table.
Once
an argument table has been established, parsing the command line is
simply a matter of calling an argtable library function which
attempts to resolve the program's argv[] arguments with the entries
of the argument table. If successful, the command line arguments are
now known to be valid and their values are ready and available for
use in their nominated program variables. If, on the other hand, the
arguments could not be successfully resolved then the program has the
choice of trying again with an alternative argument table (for
programs with multiple usages), or it can flag the error to the user
and terminate. When argtable does detect an error in the command line
it automatically generates the appropriate error message strings for
you, so error reporting is usually just a matter of printing these
strings to stdout or stderr.
Argtable also provides some
auxiliary functions that allow the programmer to generate plain text
descriptions of the individual arguments and their overall usage
syntax directly from an argument table. These make it easy to
generate useful on-line help facilities that are guaranteed to be up
to date with your code.
Perhaps best of all, argtable is simple
to use; it only has seven functions.
Argtable supports two styles of command line arguments, tagged and untagged.
Tagged arguments are identified by a prefix tag, as in -o file
or title:mystuff. The tag enables these arguments to
appear anywhere on the command line, and in any order. The format of
the tags is completely general; they appear exactly as defined in the
argument table, without being restricted to any particular formatting
style or identifying character traits. The programmer may implement
any style of argument tag desired, including such common styles as
-title mystuff, title:mystuff, --title mystuff,
or title=mystuff.
Untagged arguments on the other hand
have no prefix; they are identified strictly by their ordering on the
command line.
The two styles of arguments may be freely mixed,
whereupon the tagged arguments are always processed first, leaving
any remaining (untagged) arguments to be scanned from left to
right.
The value given to an argument may be of type integer,
double, string, or boolean. Doubles may be given
in either floating point or scientific notation, and strings may be
either quoted or unquoted. Booleans will accept any of the keywords
on, off, yes, no, true, or false
and yield an integer value of 0 (negative) or 1 (affirmative)
accordingly.
A special argument type called literal is also
provided; it yields an integer value according to the presence or
absence of a given string literal on the command line. It is useful
for specifying unparameterised command line switches such as -verbose
and -help.
Arguments may be assigned default values that take effect when no matching command line argument could be found. When you specify a default value for an argument you are, in effect, declaring that argument as being optional. Arguments without defaults are, by definition, regarded as mandatory arguments.
Argtable conforms to ansi C requirements and should compile on any standard ansi C compiler. To date, it has been successfully compiled on:
MIPSpro C/C++ on IRIX 6.2, 6.3 and IRIX64 6.2
DEC C/C++ on Digital Unix V4.0 (OSF/1)
GNU gcc/g++ on DEC Digital Unix V4.0 (OSF/1); IRIX 6.2, 6.3; IRIX64 6.2; Linux 2.0.30; and SunOS 5.5.1.
Argtable is available under the /pub/linux/devel/ directory of the ftp archives at the Metalab site of the University of North Carolina. You can go straight to http://metalab.unc.edu/pub/linux/devel/ but you are encouraged to try one of the Metalab mirror sites closest to you.
Here are some similar packages that I am aware of. Apologies for any I may have omitted.
clig, The Command Line Interpreter Generator: http://wsd.iitb.fhg.de/~kir/clighome/
opt, The Options and Parameter parsing Toolkit: ftp.lanl.gov/pub/users/jt/Software/opt/opt_toc.html
By way of example, lets say we have a program called myprog and we wish to implement the following command line usage syntax for it:
myprog [-tit <title>] grad:gradient [y-int] -tit <title> your title grad:gradient line gradient y-int y-intercept
Our program will use a single argument table to do this. All argument tables are defined as an array of arg_rec structs, with each array element pertaining to a single command line argument. The arg_rec struct is defined in argtable.h as:
typedef enum {arg_int=0,arg_dbl,arg_str,arg_bool,arg_lit} arg_type; typedef struct { const char *tagstr; /* argument tag string */ const char *argname; /* argument name string */ arg_type argtype; /* argument data type */ void *valueptr; /* ptr to user storage location */ const char *defaultstr; /* default value, as a string */ const char *argdescrip; /* argument description string */ } arg_rec; |
The following code fragment shows how the argument table is defined statically within the code. Defining the tables statically is a programming convenience but not a requirement; the table could equally well have been dynamically allocated and initialized at run time. Notice that I also chose to define the argument table within the main() block because that's the only place where it is used so there is no need to promote it to a higher namespace. However you may define it in the global namespace if you prefer.
int main(int argc, char **argv) { static char str[50]; static double grad; static int c; arg_rec argtable[] = { {"-tit ", "<title>", arg_str, str, "noname", "\t\t your title"}, {"grad:", "gradient", arg_dbl, &grad, NULL, "\t line gradient"}, {NULL, "y-int", arg_int, &c, "0", "\t\t y-intercept"} }; const size_t narg = sizeof(argtable)/sizeof(arg_rec); ... } |
Our argument table has three rows, one for each command line argument -tit <title>, grad:gradient, and y-int. The behaviour of the argument parsing is governed entirely by the contents of the various fields (columns) of the argument table. Lets step through each field one by one.
The tag string:
The first field is the argument's
tag string. It defines the prefix literal that identifies a tagged
argument. The tag string is optional, but when present it must
contain at least one non-whitespace character. Alternatively it can
be given as NULL thus declaring the argument as untagged. In
this example, -tit <title> and grad:gradient are
tagged arguments and y-int is untagged.
The name string:
The second field is the argument's name
string. It is not actually used to process the command line
arguments, rather it defines the name of the argument as it appears
in the description strings generated by the arg_syntax() and
arg_glossary() library functions. Those functions
automatically substitute any NULL names with the argument's data
type enclosed in angled brackets, as in "<int>" or
"<string>".
The data type:
The third field is an enumerated type that
defines the data type of the command line argument. Possible values
are arg_int, arg_dbl, arg_str, arg_bool,
and arg_lit. They represent integer, double,
string, boolean, and literal arguments
respectively. In our example -tit <title> expects
<title> to be substituted by a string value,
grad:gradient expects gradient to be a double, and
y-int is expected to be an integer.
The data pointer:
The fourth field is a pointer-to-void
that gives the address of the user-defined program variable used to
store the argument's value. A NULL pointer here causes the value to
be discarded once is has been scanned.
Take care that the data
type of of the target memory location matches that specified in the
previous column. Arguments of type arg_int, arg_bool,
and arg_lit must each point to an integer variable.
Those of type arg_dbl must point to a double and those
of arg_str must point to a char array. In our example,
the string value associated with -tit <title> is
written into the char str[50]
buffer, the double value associated with grad:gradient is
written into double grad, and the integer value associated
with y-int is written into int c.
The default value:
The fifth field is a string which
contains an optional default value for the argument. It is defined
as a string and automatically cast to the appropriate data type at
run time. A NULL value indicates no default. In our example, -tit
<title> and y-int have default values of "noname"
and zero respectively, whereas grad:gradient has no default
and is thus regarded as a mandatory argument.
The description string:
The sixth and final field allows
the programmer to enter a brief description of the argument. It is
these descriptions that are output by the arg_glossary()
function. A NULL value causes that entry to be omitted from the
glossary output.
Having defined the argument table, we can now use it to parse the command line arguments in argv[]. There are several ways to do this, but the simplest is to use the arg_scanargv() library function.
int arg_scanargv(int argc, char** argv, arg_rec *argtable, int n, char* CmdLine, char* ErrMsg, char* ErrMark);
This function takes as input the command line arguments in argv[] (there are argc of them) and a pointer to the argument table in argtable (which has n rows). It proceeds to scan the argv[] arguments (skipping argv[0]) and tries to resolve them with the entries given in the argument table. If this can be done successfully then all argument values are written into the program variables as designated by the argument table and the function returns 1. If not, the function returns 0 to indicate the failure.
The three string pointers CmdLine, ErrMsg, and ErrMark refer to user defined string buffers which arg_scanargv() uses to return information about the parsing. They are optional parameters for arg_scanargv() in the sense that they may be given as NULL if you do not wish to use them. Into the first string buffer, *CmdLine, is written a copy of the command line arguments from argv[] concatenated into a single line string with the arguments seprated by spaces. This string is written before the parsing commences, so it will always be present whether or not the parsing was succsessful. The CmdLine string is useful when the calling program wants to echo the command line to stdout or stderr, say after an error has occurred. The string buffers ErrMsg and ErrMark are only used when arg_scanargv() detects an error in the command line. When this happens, an error message describing the reason for the failure is written into *ErrMsg. The ErrMark string is somewhat related to CmdLine; it contains a string of tilde characters which have been formatted in such a way as to highlight the exact location of the error in the command line when it is displayed directly below the *CmdLine string (assuming a fixed width font is used).
The code fragment below demonstrates the use of the arg_scanargv() function. It presumes that argc, argv, argtable, and narg are as defined in the example above and are accessable in this namespace.
{ char cmdline[200], errmsg[200], errmark[200]; if (!arg_scanargv(argc,argv,argtable,narg,cmdline,errmsg,errmark)) { /*-- arg error occurred, print error message and exit --*/ printf("ERROR: %s\n", cmdline); printf(" %s %s\n", errmark, errmsg); return 1; } /*-- only get here if the arguments were scanned successfully --*/ } |
And here are some examples of the console output that this code produces when arg_scanargv() returns zero.
$ myprog grad:oops ERROR: myprog grad:oops ^ invalid grad:gradient argument $ myprog grad:13 nope ERROR: myprog grad:13 nope ^^^^ unexpected argument $ myprog grad:13 99 uh oh ERROR: myprog grad:13 99 uh oh ^^ unexpected argument
There are two more functions from the argtable library of interest to us, namely, arg_syntax() and arg_glossary(). These functions generate plain text descriptions of the argument syntax as well as descriptions of the individual arguments and are useful for writing out online help text to the console
const char* arg_syntax(const arg_rec* argtable, int n); const char* arg_glossary(const arg_rec* argtable, int n, const char* prefix);
The arg_syntax() function returns a pointer to an internal string buffer that contains a plain text description of the usage syntax of the argument table it was passed. The string comprises a space separated list of the tag and name strings of each argument table entries concatenated into a single line string. Optional command line arguments are automatically enclosed in square brackets. Calling arg_syntax() on our example argument table would return the string:
[-tit <title>] grad:gradient [y-int]
The calling program would ordinarily prepend this with the program name from argv[0] to get the full usage syntax.
The arg_glossary() function is similar, except it generates a multi-line text string with one argument per line. Each line includes the argument's tag, its name string, and its description string as given in the argument table. Arguments that have a NULL description string are omitted. Each line of the glossary string is prefixed with the string given in the prefix parameter; it useful for indenting each line of the string. Calling arg_glossary() with our example argument table results in the following multi-line string:
-tit <title> your title grad:gradient line gradient y-int y-intercept
Lets return to our example program and put it all together in its
entirety.
Our program, when executed without any arguments
(argc==1), will display the argument usage syntax and a description
of the individual arguments on
stdout, then exit. When given a valid set of arguments, it will
display the resulting argument values as they are stored in the local
program variables. Otherwise, it echoes the erroneous command line
together with an appropriate error message to stdout and terminates
with error code 1.
#include "argtable.h" int main(int argc, char **argv) { static char str[50]; static double grad; static int c; arg_rec argtable[] = { {"-tit ", "<title>", arg_str, str, "noname", "\t\t your title"}, {"grad:", "gradient", arg_dbl, &grad, NULL, "\t line gradient"}, {NULL, "y-int", arg_int, &c, "0", "\t\t y-intercept"} }; const size_t narg = sizeof(argtable)/sizeof(arg_rec); /*-- process the command line args --*/ if (argc==1) { /*-- display program usage and exit. --*/ printf("Usage: %s %s\n", argv[0], arg_syntax(argtable,narg)); printf("%s\n",arg_glossary(argtable,narg," ")); return 0; } else { /*-- scan command line arguments from argv[] --*/ char cmdline[200], errmsg[200], errmark[200]; if (!arg_scanargv(argc, argv, argtable, narg, cmdline, errmsg, errmark)) { /*-- arg error occurred, print error message and exit --*/ printf("ERROR: %s\n", cmdline); printf(" %s %s\n", errmark, errmsg); return 1; } } /*-- get here only if command line args ok --*/ printf("title: \"%s\"\n",str); printf("gradient: %f\n",grad); printf("y-intercept: %d\n",c); return 0; } |
Here are some results of running myprog with various command line arguments.
$ myprog Usage: myprog [-tit <title>] grad:gradient [y-int] -tit <title> your title grad:gradient line gradient y-int y-intercept $ myprog grad:10 title: "noname" gradient: 10.000000 y-intercept: 0 $ myprog 7 grad:1.234 title: "noname" gradient: 1.234000 y-intercept: 7 $ myprog -tit "hello world" grad:3.33 11 title: "hello world" gradient: 3.330000 y-intercept: 11
For more information on any of the argtable library functions, consult the unix man pages that come supplied with the library. They are provided in both nroff and html format. The functions are summarized below:
arg_catargs() |
Creates a single line string of space separated arguments by concatenating copies of the argv[] arguments. |
arg_dump() |
Prints the contents of an argument table. This is useful for debugging |
arg_glossary() |
Generates a multi-line glossary string from an argument table. |
arg_record() |
Returns an arg_rec struct pre-filled with a set of given values. |
arg_scanargv() |
Scans arguments from argv[] and parses them according to a given argument table. |
arg_scanargstr() |
Scans arguments from a single line string and parses them acording to a given argument table. |
arg_syntax() |
Generates a single line string describing an argument table's usage syntax. |
arg_typestr |
A static array of strings defining the default names assigned to different argument types. |
Having untarred the argtable distribution file (argtable-1.2.tar), detailed instructions on how to install the package can be found in the argtable/INSTALL text file. However in most cases all you need do is:
cd to the argtable/ directory.
Execute the configure script.
Type make to build the executables and libraries.
Type make check to run the system tests (optional).
You ought to see some messages like this:
cd
src; make check
make[1]: Entering directory
`/home/heitmann/Projects/argtable-1.2/src'
RUNNING TESTS test10
test10cc test11 test11cc test12 test12cc
test10 >
/dev/null
test10cc > /dev/null
test11 >
/dev/null
test11cc > /dev/null
test12cc >
/dev/null
test12cc > /dev/null
ALL TESTS PASSED OK
make[1]:
Leaving directory `/home/heitmann/Projects/argtable-1.2/src'
As root user, type make install to install the
binaries and system documentation under /usr/local/.
Read
the INSTALL file if you wish to know how to install to an
alternative location.
Note: You may use the make uninstall
command (as root user) at anytime to safely undo the effect of a
make install.
Type make clean to remove the binaries and object files from the source directory (optional).
Argtable-1.0 released November 13, 1998.
Argtable-1.1
released January 20, 1999.
This version fixes some
cross-platform compilation errors, and saw the introduction of the
multi-platform configuration. It also saw the addition of the
arg_record() function and a change to the arg_scanargv()
function so that it no longer requires argv[0] to be the first entry
of the argument table. To maintain backwards compatibility, programs
written for version 1.0 should now define the macro
#define
ARGTABLE_COMPATIBILITY_10
prior to including the
argtable.h header file.
Argtable-1.2
released August 5, 1999.
The original makefiles have
been replaced by autoconf makefiles. The char pointers in the
argument table have been redefined as pointers to const.char.
Some of argtable's internal string buffers have been made larger to
accommodate long command lines, and a bug that occurred when program
names contained whitespace has been fixed. The documentation has
also been revised.
Argtable-2.0 coming
soon.
Argtable-2.0 (or should that be
argtable-2000?) will be a major overhaul of the code. The
changes are required to address the most common complaint about
argtable; the potential for buffer overruns as argtable writes into
fixed size string buffers. The redesign will bring some inevitable
changes to the library interface, but the basic look and feel of the
argument tables will stay the same.
Argtable is maintained by me, Stewart Heitmann. It is a tool I have felt has been long overdue in the C/C++ world, and having written it I hope that you will find it as useful as I do. If you have any comments or suggestions for improvements please email me at Stewart.Heitmann@tip.csiro.au, I'd love to hear from you. Happy parsing!