Where does getopt_long store an unrecognized option?
Asked Answered
H

2

15

When getopt or getopt_long encounters an illegal option, it stores the offending option character in optopt. When the illegal option is a long option, where can I find out what the option was? And does anything meaningful get stored in optopt then?

I've set opterr = 0 to suppress the automatically printed error message. I want to create my own message that I can print or log where I'd like, but I want to include the name of the unrecognized option.

Haw answered 27/4, 2010 at 18:25 Comment(0)
B
7

The closest I can find is that if you get a BADCH return the argv item that caused it is in argv[optind-1]. Seems like there should be a better way to find the problem argument.

Bowse answered 27/4, 2010 at 21:30 Comment(0)
H
7

You're quite right that the man page glosses right over these details, but enough hints can be gleaned from the source code, e.g., glibc's implementation in glibc-x.y.z/posix/getopt.c's _getopt_internal_r. (Perhaps that's the only interesting implementation of this GNU extension function?)

That code sets optopt to 0 when it encounters an erroneous long option, which I guess is useful to distinguish this case from an erroneous short option, when optopt will surely be non-NUL.

The error messages produced when opterr != 0 mostly print out the erroneous long option as argv[optind], and later code (always or -- conservatively -- at least mostly) later increments optind before returning.

Hence consider this program:

#include <getopt.h>
#include <stdio.h>

int main(int argc, char **argv) {
  struct option longopts[] = {
    { "foo", no_argument, NULL, 'F' },
    { NULL, 0, NULL, 0 }
  };
  int c;

  do {
    int curind = optind;
    c = getopt_long(argc, argv, "f", longopts, NULL);
    switch (c) {
    case 'f':  printf("-f\n");  break;
    case 'F':  printf("--foo\n");  break;
    case '?':
      if (optopt)  printf("bad short opt '%c'\n", optopt);
      else  printf("bad long opt \"%s\"\n", argv[curind]);
      break;
    case -1:
      break;
    default:
      printf("returned %d\n", c);
      break;
    }
  } while (c != -1);

  return 0;
}

$ ./longopt -f -x --bar --foo
-f
./longopt: invalid option -- 'x'
bad short opt 'x'
./longopt: unrecognized option '--bar'
bad long opt "--bar"
--foo

Thus in these cases, by caching the pre-getopt_long value of optind, we're easily able to print out the same bad options as the opterr messages.

This may not be quite right in all cases, as the glibc implementation's use of its own __nextchar rather than argv[optind] (in the "unrecognized option" case) deserves study, but it should be enough to get you started.

If you think carefully about the relationship between optind and the repeated invocations of getopt_long, I think printing out argv[cached_optind] is going to be pretty safe. optopt exists because for short options you need to know just which character within the word is the problem, but for long options the problem is the whole current word (modulo stripping off option arguments of the form =param). And the current word is the one that getopt_long is looking at with the (incoming) optind value.

In the absence of a guarantee written in the documentation, I would be somewhat less sanguine about taking advantage of the optopt = 0 behaviour though.

Hoffer answered 27/4, 2010 at 21:50 Comment(2)
This looks promising, but upon testing it, it seems that this solution is more sensitive to the argument permutation that getopt_long does, at least on my Solaris system. I'm sticking with optind - 1.Haw
If you are very picky (like me), you can remove the parameter before displaying with char *eq = index(argv[optind-1], '='); if (eq != NULL) *eq = '\0';.Angry

© 2022 - 2024 — McMap. All rights reserved.