CS 3210 - Lab 14 - Parsing Command-Line Options

In the previous lab, you guys wrote a program that handled -a, -d, -g, and -l as command-line options. Command-line options are a hallmark of nearly all UNIX programs, so in this lab we will learn a more efficient way to handle them.

The chapter correponding to this lab is Chapter 24.

I'll show three different ways to parse command line options: getopt, getopt_long, and popt. They all have a very similar interface.

Let The Parsing Commence

Simple Parsing With getopt

To more expediciously parse single-character command-line arguments, you can use getopt passing it argc, argv, and a string containing all valid arguments. It will return a character of the current, legal option so you can test it in a switch statement. A global variable optind contains the current index into argv. If you type two dashes ('--') option processing stops. It's all spelled out in the man page. ("man 3 getopt")

Example Program: getopt.c - An example program that calls getopt, using a while / switch combo to handle all of them. Note how '?' and 'h' are also handled to display a help message. Post-processing, the program uses optind as an index into argv to list all the non-option arguments.

If you want to pass options to your arguments, you add a colon after the character. If you want to support an optional argument, you can pass two colons. Again, the man page tells all.

Example Program: getopt-args.c - Same as the previous example, but the 'b' and 'c' options require arguments and the 'd' option supports an optional argument. I think there's a bug here, but I've coded a workaround.

Handling Long Options With getopt_long

Some programs support "long" command-line options in the form of --option. (Do "grep --help" for an example.) If you want your program to support long options, you can use getopt_long, passing it arguments similar to getopt, plus a table containing long option information.

Example Program: getopt_long.c - As the previous examples, but supports a long option that is equivalent to the short option.

One nice thing about getopt_long is that it allows you to set a "flag" variable, indicating whether the option is "on" or "off".

Example Program: gnu_long.c - An example from the GNU C library documentation. In addition to showing how flag-setting can be done (with --verbose or --brief), note that there is no correlation between the short and long options in this example.

Handling Non Command-Line Options With popt

As your book explains on p. 445, there are some programs that require option parsing, but the options are not passed on the command line; the lftp program is an example. (I'll demo this in class.) For this purpose, the popt library was developed. It's also a bit more straightforward than the getopt_long interface, which requires duplication of information. The popt library was written by one of the authors of your book. Several other features and advantages of popt are bulletted at the bottom of page 445. There is also a man page for popt.

Example Program: popt.c - Uses popt to parse either long or short command-line options without any arguments. Note that we must link with the popt library by passing -lpopt to the compiler.

Another nice thing about popt is that it can automatically load variables, as with getopt_long, but can handle different types of variables. An incomplete listing of argument types is given on p. 447 in your book. The man page is a bit more up to date.

Example Program: popt-args.c - Same program as the previous, but loads different variables with different arguments.

Yet Another Example Program: popt_man.c - This one is from the man page for popt.

I am totally ignoring the stuff on "Option Aliasing" in your book on p. 452. You should ignore it too.

Further Study

The man page for getopt.

GNU C library documentation for Parsing Long Options.

The man page for popt and maybe this little writeup on Command Line Parsing With popt.

Your book, Chapter 24

When is a command line not a line? an editorial article on Freshmeat on how to securely parse command-line agruments with attention paid to various approaches that have been invented (getopt and popt are used as examples).

The indent program on Gautama is very possibly the most command-line option-laden program in existence. (gcc is right up there, though.)

Lab 14

For this lab, you will convert the previous lab to use popt, allowing for both short and long command line options, which will be as follows:

Usage: dbpopt [OPTION...]
  -a, --add=KEY,VAL       add a key/value pair
  -d, --delete=DELKEY     delete a record by key
  -g, --get=GETKEY        get a record by key
  -l, --list              list all records

Help options:
  -?, --help              Show this help message
  --usage                 Display brief usage message

The popt table you set up will automatically generate this help text for you when you type dbpopt --help or dbpopt -? from the command line. You should find that using popt simplifies your program somewhat.

Some Steps to Help You

  1. First, copy over and rename your previous lab with cp ../lab13/dblab.c dbpopt.c. Copy your previous Makefile in a similar manner and edit it to change instances of dblab to dbpopt.
  2. At the top of your program, set up a popt option table as we've seen in the previous examples. Be sure to include the POPT_AUTOHELP macro second-to-last in the list to automatically generate the above help message from your option table.
  3. Call poptGetContext to get ready for parsing.
  4. Use the while ... switch approach to parsing the command-line options that we saw in the previous examples. There should be one case statement for each operation you're going to perform (i.e. one for 'a', one for 'd', one for 'g' and one for 'l').
  5. Within each of the case statements, copy-and-paste your code from the previous lab that performed all the various Berkeley DB operations. You should be able to delete some of the arg-checking code from the previous assignment, because popt handles everything for you.
  6. At the end of the program, clean up by calling poptFreeContext and db->close(db).

Save this in a file called dbpopt.c. The whole file should be about 100 or so lines long, but it will largely be a copy-and-paste of your previous lab with some popt stuff added. As before, you can do everything in just the main funciton, you don't need any other functions. (Some of you will still want to complicate things by using lots of support functions. Makes me wonder what you guys do for fun...) Be sure to have a Makefile as well. Put them both in a lab14/ directory.

How do I handle two option arguments?

The alert student will wonder how to handle passing 2 arguments to the -a or --add option when popt only allows you to pass one. To solve this dilemma, make it so that the user must enter in the key and value seperated by a comma rather than a space as shown above like so: KEY,VAL. Thus an invocation might look like:

dbpopt --add Fred,husband
Then, in your code that handles the -a flag, use the following code to extract each part:
/* declare some variables (probably at the top of your program */
int comma_idx;
char keystr[1024];
char valstr[1024];

/* extract each piece */
comma_idx = strcspn(addstr, ",");
strncpy(keystr, addstr, comma_idx);
keystr[comma_idx] = '\0';
strcpy(valstr, &addstr[comma_idx + 1]);

/* load up the DBT key and value with the strings just extracted */
key.data = keystr;
key.size = strlen(key.data) + 1; /* for trailing '\0' */
value.data = valstr;
value.size = strlen(value.data) + 1;

/* now call db->put */
...

Output

Here are some example tests:

$ ./dbpopt --help
Usage: dbpopt [OPTION...]
  -a, --add=KEY,VAL       add a key/value pair
  -d, --delete=DELKEY     delete a record by key
  -g, --get=GETKEY        get a record by key
  -l, --list              list all records

Help options:
  -?, --help              Show this help message
  --usage                 Display brief usage message
$ ./dbpopt --usage
Usage: dbpopt [-l?] [-a KEY,VAL] [-d DELKEY] [-g GETKEY] [--usage]
$ ./dbpopt -l
$ ./dbpopt -a Fred,Husband -a Wilma,wife -l
Wilma = wife
Fred = Husband
$ ./dbpopt --add "Pebbles,baby girl" --add Dino,pet --list
Wilma = wife
Dino = pet
Fred = Husband
Pebbles = baby girl
$ ./dbpopt --get Fred --delete Fred --list
Fred = Husband
Wilma = wife
Dino = pet
Pebbles = baby girl
$ ./dbpopt -l
Wilma = wife
Dino = pet
Pebbles = baby girl

If you can run the same tests with your program and get the same results, you probably did it right.

Files

Here is a list of the files created in this lab.

Please turn in just the dbpopt.c file with your name, lab # and class # at the top.