A magic getopt

Parsing command lines in C is easy when all of the options are single characters: You pass your command line to getopt along with a string containing all the valid options; then you have a switch statement with a case for each option you want to handle. It looks something like this:
        int ch;
        while ((ch = getopt(argc, argv, ":af:")) != -1) {
                switch (ch) {
                case 'a':
                        aflag = 1;
                        break;
                case 'f':
                        printf("foo: %s\n", optarg);
                        break;
                case ':':
                        printf("missing argument to -%c\n", optopt);
                        /* FALLTHROUGH */
                default:
                        usage();
                }
        }
Unfortunately if you want to add support for long options — say, to accept a new --bar option — you need to switch to using getopt_long and your list of options is no longer confined to the options-processing loop:
enum options
{
	OPTION_BAR
};

...

static struct option longopts[] =
{
	{ "bar", required_argument, NULL, OPTION_BAR }
};

...

        int ch;
        while ((ch = getopt_long(argc, argv, ":af:", longopts, NULL)) != -1) {
                switch (ch) {
                case 'a':
                        aflag = 1;
                        break;
		case OPTION_BAR:
			printf("bar: %s\n", optarg);
			break;
                case 'f':
                        printf("foo: %s\n", optarg);
                        break;
                case ':':
                        printf("missing argument to -%c\n", optopt);
                        /* FALLTHROUGH */
                default:
                        usage();
                }
        }
Rather than adding a new option in one place (or two, if you count the list of options at the top of the loop as being a separate place), new long options require changes in three places — one of which (the enum) is often placed in an entirely separate file. So much for keeping code clean and free of duplication. There has got to be a better way, right?

Enter magic getopt. Via a little bit of macro magic, the above options-handling code turns into this:

        const char * ch;
        while ((ch = GETOPT(argc, argv)) != NULL) {
                GETOPT_SWITCH(ch) {
                GETOPT_OPT("-a"):
                        aflag = 1;
                        break;
                GETOPT_OPTARG("--bar"):
                        printf("bar: %s\n", optarg);
                        break;
                GETOPT_OPTARG("-f"):
                        printf("foo: %s\n", optarg);
                        break;
                GETOPT_MISSING_ARG:
                        printf("missing argument to %s\n", ch);
                        /* FALLTHROUGH */
                GETOPT_DEFAULT:
                        usage();
                }
        }
with each option listed just once, at the point where it is handled.

To use this, add getopt.c and getopt.h to your project, #include "getopt.h", and then make the following changes:

And that's it. These changes can be made to a program which accepts single-character options almost mechanically and with no increase in the source code complexity; and then new options (short or long) can be added by simply adding the new option-handling code, without needing to make changes in several different places.

I think it's time for me to start adding long options to some of my projects.

Posted at 2015-12-06 23:45 | Permanent link | Comments
blog comments powered by Disqus

Recent posts

Monthly Archives

Yearly Archives


RSS