myprog [-tit <title>] grad:gradient [y-int] -tit <title> your title grad:gradient line gradient y-int y-intercept
We will create a single argument table in our program that defines the argument properties, and pass that table along with argc and argv[] to the arg_scanargv() function to do the parsing.
Defining the argument table.
An argument table is just 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;
Thus we may define our argument table statically in the code as follows:
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); ... }
Defining the tables statically is a programming convenience but not a requirement; the table could equally well have been dynamically allocated and initialized at runtime. 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.
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.
int arg_scanargv(int argc, char** argv, arg_rec *argtable, int n, char* CmdLine, char* ErrMsg, char* ErrMark);
It 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 failure.
The three string pointers CmdLine, ErrMsg, and ErrMark refer to user defined string buffers in which arg_scanargv returns information about the parsing. They are optional parameters in the sense that they may be given as NULL if you do not wish to use them.
CmdLine is always assigned a copy of the orginal command line, concatenated into a single space-separated string.
ErrMsg and ErrMark are only used when arg_scanargv detects a parse error in the command line. In those cases, ErrMsg is assigned an explantory error message string, and ErrMark is assigned a string of tilde characters which have been formatted in such a way as to highlight the exact location of the error in CmdLine when printed one atop the other.
The code fragment below demonstrates the use of arg_scanargv. It presumes that argc, argv, argtable, and narg are as defined in the example above.
{ 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 detects a parse error.
$ 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
Generating online help.
The arg_syntax() and arg_glossary() functions take an argument table and generate plain text descriptions of its command line syntax as well as descriptions of the individual arguments. These are useful for displaying help text to the user.
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 the program name from argv[0] to this to get the full command line 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
Putting it all together.
Lets return to our example program and put it all together in its entirety. Our program, when executed without any arguments (argc==1), will print the argument usage syntax and a glossary 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 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
Argtable (http://argtable.sourceforge.net)