Understanding `option long_options[]` when using `getopt_long`
Asked Answered
C

1

7

I am trying to learn to use getopt_long. From wikipedia, I see the code

#include <stdio.h>     /* for printf */
#include <stdlib.h>    /* for exit */
#include <getopt.h>    /* for getopt_long; POSIX standard getopt is in unistd.h */
int main (int argc, char **argv) {
    int c;
    int digit_optind = 0;
    int aopt = 0, bopt = 0;
    char *copt = 0, *dopt = 0;
    static struct option long_options[] = {
        {"add", 1, 0, 0},
        {"append", 0, 0, 0},
        {"delete", 1, 0, 0},
        {"verbose", 0, 0, 0},
        {"create", 1, 0, 'c'},
        {"file", 1, 0, 0},
        {NULL, 0, NULL, 0}
    };
    int option_index = 0;
    while ((c = getopt_long(argc, argv, "abc:d:012",
                 long_options, &option_index)) != -1) {
        int this_option_optind = optind ? optind : 1;
        switch (c) {
        case 0:
            printf ("option %s", long_options[option_index].name);
            if (optarg)
                printf (" with arg %s", optarg);
            printf ("\n");
            break;
        case '0':
        case '1':
        case '2':
            if (digit_optind != 0 && digit_optind != this_option_optind)
              printf ("digits occur in two different argv-elements.\n");
            digit_optind = this_option_optind;
            printf ("option %c\n", c);
            break;
        case 'a':
            printf ("option a\n");
            aopt = 1;
            break;
        case 'b':
            printf ("option b\n");
            bopt = 1;
            break;
        case 'c':
            printf ("option c with value '%s'\n", optarg);
            copt = optarg;
            break;
        case 'd':
            printf ("option d with value '%s'\n", optarg);
            dopt = optarg;
            break;
        case '?':
            break;
        default:
            printf ("?? getopt returned character code 0%o ??\n", c);
        }
    }
    if (optind < argc) {
        printf ("non-option ARGV-elements: ");
        while (optind < argc)
            printf ("%s ", argv[optind++]);
        printf ("\n");
    }
    exit (0);
}

I don't quite understand the option long_options[] object.

First column

I think the first "column" of long_options[] should be the long flag (whatever follows --) the user have used in the command line.

Second column

I would have thought the second column should contain only no_argument, required_argument, or optional_argument but instead of that I see 0 and 1.

Third column

I don't understand the third column.

Fourth column and maximum number of flags

The fourth column is the unique identifier one uses in the switch statement. However, if the unique identifier can only be a single character, then are we limited to all lower case letters (26) + all uppercase letters (26) + the numbers (10) + eventually some special characters for a total of a bit more than 62 different arguments max? Is this a limitation of getopt? If I am mistaken, then how can one indicate more than two characters to identify a flag in the third argument to getopt_long (""abc:d:012"")

I suppose the last row of option long_options[] is for when getopt returns -1 and therefore does not really matter as long as it exists.

Cochrane answered 10/10, 2016 at 20:11 Comment(7)
C++ Is not C...Arsenic
I suppose you are reffering to my use of the tag C. That was a mistake, I meant C++. (tag edited). ThanksCochrane
@Cochrane You had it as C++ the first time. amanuel2 edited your question to change the C++ tag to C. The code works in either language. If you're trying to understand it specifically as C++ code, the C++ tag is appropriate, just beware that the code does not demonstrate C++ best practices and you will likely receive comments about that.Dentil
@hvd Oh ok, that makes sense. I am aiming to code in C++ but I will be happy to understand how getopt works in pure C and I'll consider later best practices in C++. I edited the tag back to C. Thank youCochrane
@Cochrane i edited it to C , because C++ isn't really being used here.. as you are using printf and so on.. I assumed you mistagged and put it as C++.Arsenic
Wikipedia is not the manual; which part of the manual for getopt_long() is causing problems?Protuberant
There's no reason to guess about the meanings of the members of struct option. Refer to the real documentation of getopt_long(), available in many places, apparently not including Wikipedia. Here, for instance, or here, not to mention probably on your own machine via the man command.Manufacture
T
14

The struct option array is precisely defined in man getopt_long [Note 1] from which I excerpt:

longopts is a pointer to the first element of an array of struct option declared in <getopt.h> as

   struct option {
       const char *name;
       int         has_arg;
       int        *flag;
       int         val;
   };

The meanings of the different fields are:

name is the name of the long option.

has_arg is: no_argument (or 0) if the option does not take an argument; required_argument (or 1) if the option requires an argument; or optional_argument (or 2) if the option takes an optional argument.

flag specifies how results are returned for a long option. If flag is NULL, then getopt_long() returns val. (For example, the calling program may set val to the equivalent short option character.) Otherwise, getopt_long() returns 0, and flag points to a variable which is set to val if the option is found, but left unchanged if the option is not found.

val is the value to return, or to load into the variable pointed to by flag.

The last element of the array has to be filled with zeros.

So, you would normally use a symbolic constant for the second element (has_arg), but the manpage allows you to use 0, 1 or 2, presumably for backwards-compatibility. (Wikipedia should use the symbolic constants, IMHO, but that's between Wikipedia and its editors.)

getopt_long returns an int, not a char. If the flag (third) field is NULL (or, equivalently, 0), then the val (fourth) field will be returned, and that can be anything which fits into an int. A character certainly fits into an int, so you could return the equivalent short option character (as noted in the manpage) but you are under no obligation to do so. getopt also returns an int, but since it always returns an option character (or an error indication), there are a large number of int values which it will never return. [Note 2]

If the third field is not NULL, it is expected to point to a variable of type int into which getopt_long will store the val value. That could be used, for instance, for boolean flags:

enum FROBNICATE { FROB_UNSET = -1, FROB_NO = 0, FROB_YES = 1 };
/* ... */

/* This is conceptually an enum, but `getopt_long` expects an int */
int frob_flag = FROB_UNSET;

struct option long_opts = {
  /* ... */
  {"frobnicate", no_argument, &frob_flag, FROB_YES},
  {"unfrobnicated", no_argument, &frob_flag, FROB_NO},
  /* ... */
  {NULL, 0, NULL, 0}
};

/* Loop over arguments with getopt_long;
   In the switch statement, you can ignore the returned value
   0 because the action has been fully realized by setting the
   value of a flag variable.
 */

if (frob_flag == FROB_UNSET)
  frob_flag = get_default_frobnication();

As indicated by the manpage, the last entry in the array must be all zeros (or NULL for the pointer members). That is needed so that getopt_long knows where the array ends.

Notes

  1. You probably have the manpages installed on your system, in which case you can simply type man getopt_long to see the documentation for getopt_long. That should work for any standard C library function, any Gnu libc function, and, in general, any C library function for which you have installed the -doc package. (Highly recommended.) On the whole, you should try a manpage first before looking at Wikipedia, because the manpage will be the documentation for the version of the library function actually installed on your system.

  2. The fact that a function returns a given datatype does not imply that it might return any possible value of that datatype.

Textile answered 10/10, 2016 at 21:48 Comment(3)
Using an enum as a flag does not seem to work in C++. I get a error: cannot convert 'FROBNICATE*' to 'int*'. How should I work around this?Hoffmann
It looks to me like g++ should be making an implicit conversion from the unscoped enum to int*. Even explicitly setting the type to int does not work: enum FROBNICATE : int { FROB_UNSET = -1, FROB_NO = 0, FROB_YES = 1 };Hoffmann
@Mike: My bad, and it will be much more evident in C++ because it's (correctly) pickier about enums. SInce option::flag has type int*, the variable you store the flag value in needs to be an int, even if its value is conceptually an enum. If you use type safe enums in C++ then you'll need to add casts as well, which would be annoying. I changed the type in the answer, but no casts (since the question is still C). Thanks.Textile

© 2022 - 2024 — McMap. All rights reserved.