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: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: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(); } }
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?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(); } }
Enter magic getopt. Via a little bit of macro magic, the above options-handling code turns into this:
with each option listed just once, at the point where it is handled.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(); } }
To use this, add getopt.c and getopt.h to your project, #include "getopt.h", and then make the following changes:
- The option character (ch in the examples above) turns into an option string.
- The function getopt is replaced by the macro GETOPT, which no longer needs the third parameter (the string containing the option characters to accept) and returns NULL upon reaching the end of the options list instead of -1.
- Instead of switch (ch) you now need GETOPT_SWITCH(ch).
- case 'a' turns into GETOPT_OPT("-a") for options without arguments, or GETOPT_OPTARG("-a") for options with arguments.
- case ':' turns into GETOPT_MISSING_ARG.
- default turns into GETOPT_DEFAULT, and this must be present at the end of the switch statement in order for the magic to work.
- In the unlikely scenario that you had several case labels on the same line: Every GETOPT_* label needs to be on a different source code line.
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