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.
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.
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.
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.
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.)
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.
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.
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,husbandThen, 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 */ ...
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.
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.