getopt does not parse optional arguments to parameters
Asked Answered
A

4

54

In C, getopt_long does not parse the optional arguments to command line parameters parameters.

When I run the program, the optional argument is not recognized like the example run below.

$ ./respond --praise John
Kudos to John
$ ./respond --blame John
You suck !
$ ./respond --blame
You suck !

Here is the test code.

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

int main(int argc, char ** argv )
{
    int getopt_ret, option_index;
    static struct option long_options[] = {
               {"praise",  required_argument, 0, 'p'},
               {"blame",  optional_argument, 0, 'b'},
               {0, 0, 0, 0}       };
    while (1) {
        getopt_ret = getopt_long( argc, argv, "p:b::",
                                  long_options,  &option_index);
        if (getopt_ret == -1) break;

        switch(getopt_ret)
        {
            case 0: break;
            case 'p':
                printf("Kudos to %s\n", optarg); break;
            case 'b':
                printf("You suck ");
                if (optarg)
                    printf (", %s!\n", optarg);
                else
                    printf ("!\n", optarg);
                break;
            case '?':
                printf("Unknown option\n"); break;
        }
    } 
    return 0;
}
Argybargy answered 27/6, 2009 at 12:15 Comment(1)
I'm documenting this here with the answer, so other people does not need to bang their head against the wall.Argybargy
A
101

Although not mentioned in glibc documentation or getopt man page, optional arguments to long style command line parameters require 'equals sign' (=). Space separating the optional argument from the parameter does not work.

An example run with the test code:

$ ./respond --praise John
Kudos to John
$ ./respond --praise=John
Kudos to John
$ ./respond --blame John
You suck !
$ ./respond --blame=John
You suck , John!
Argybargy answered 27/6, 2009 at 12:18 Comment(8)
Note that Perl's Getopt::Long module does NOT have the same requirement. Boost.Program_options does.Mobcap
Wow, this sucks. I ran into the same problem with regular getopt() and when using an optstring "a::", optarg would only be set if you have ZERO space between the option and the argument such as '-afoo'Bernat
At least for my short options like -a or -a=300, I needed to add an if (optarg[0] == '=') {memmove(optarg, optarg+1, strlen(optarg));} to strip away the =. Otherwise, I would always have had =300 in optarg. Or I would have needed to call it like this: -a300 - which is quite ugly I think. Thanks for your answer anyway, it helped me a lot!Acrostic
In this case it's better not to use optional arguments. Is this a bug or a feature? Now I have an urge to ./respond --blame=glibc.Dicky
I wanted to downvote but then realized that your answer is great; it's just the behaviour I want to downvote. Thanks!Crosspurpose
But why? It's explicitly encouraged by sites founder Jeff Atwood. stackoverflow.blog/2011/07/01/…Argybargy
To the people complaining about this feature: if an option has an optional argument, there is no way to distinguish between an option to that argument, and an extra argument that comes after options.Casualty
Accidentally sent my comment before I'd finished writing it.. Say I have a program with this usage: program [--option [<arg>]] [<argument>] If you run program --option something, it is ambiguous whether something is the <arg> of --option, or the <argument> of the program. The only way to remove the ambiguity is to force <arg> to be attached to --option. The developers of glibc have given us sane defaults here instead of undefined behaviour... Developers using getopt are free to create their own logic if they want to handle it in a different way that works for them.Casualty
W
18

The man page certainly doesn't document it very well, but the source code helps a little.

Briefly: you're supposed to do something like the following (though this may be a little over-pedantic):

if(   !optarg
   && optind < argc // make sure optind is valid
   && NULL != argv[optind] // make sure it's not a null string
   && '\0' != argv[optind][0] // ... or an empty string
   && '-' != argv[optind][0] // ... or another option
  ) {
  // update optind so the next getopt_long invocation skips argv[optind]
  my_optarg = argv[optind++];
}
/* ... */

From among the comments preceding _getopt_internal:

...

If getopt finds another option character, it returns that character, updating optind and nextchar so that the next call to getopt can resume the scan with the following option character or ARGV-element.

If there are no more option characters, getopt returns -1. Then optind is the index in ARGV of the first ARGV-element that is not an option. (The ARGV-elements have been permuted so that those that are not options now come last.) <-- a note from me: if the 3rd argument to getopt_long starts with a dash, argv will not be permuted

...

If a char in OPTSTRING is followed by a colon, that means it wants an arg, so the following text in the same ARGV-element, or the text of the following ARGV-element, is returned in optarg. Two colons mean an option that wants an optional arg; if there is text in the current ARGV-element, it is returned in optarg, otherwise optarg is set to zero.

...

... though you have to do some reading between the lines. The following does what you want:

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

int main(int argc, char* argv[] ) {
  int getopt_ret;
  int option_index;
  static struct option long_options[] = {
      {"praise",  required_argument, 0, 'p'}
    , {"blame",  optional_argument, 0, 'b'}
    , {0, 0, 0, 0}
  };

  while( -1 != ( getopt_ret = getopt_long(  argc
                                          , argv
                                          , "p:b::"
                                          , long_options
                                          , &option_index) ) ) {
    const char *tmp_optarg = optarg;
    switch( getopt_ret ) {
      case 0: break;
      case 1:
        // handle non-option arguments here if you put a `-`
        // at the beginning of getopt_long's 3rd argument
        break;
      case 'p':
        printf("Kudos to %s\n", optarg); break;
      case 'b':
        if(   !optarg
           && NULL != argv[optind]
           && '-' != argv[optind][0] ) {
          // This is what makes it work; if `optarg` isn't set
          // and argv[optind] doesn't look like another option,
          // then assume it's our parameter and overtly modify optind
          // to compensate.
          //
          // I'm not terribly fond of how this is done in the getopt
          // API, but if you look at the man page it documents the
          // existence of `optarg`, `optind`, etc, and they're
          // not marked const -- implying they expect and intend you
          // to modify them if needed.
          tmp_optarg = argv[optind++];
        }
        printf( "You suck" );
        if (tmp_optarg) {
          printf (", %s!\n", tmp_optarg);
        } else {
          printf ("!\n");
        }
        break;
      case '?':
        printf("Unknown option\n");
        break;
      default:
        printf( "Unknown: getopt_ret == %d\n", getopt_ret );
        break;
    }
  }
  return 0;
}
Welter answered 14/9, 2015 at 23:14 Comment(3)
This worked really well, thanks. Not sure where you got optindex from; it's called (extern int) optind for me.Nimrod
There is an error in the second code example, it should be optind instead of optindex.Fantastic
Looks like the code should maintain optindex. Otherwise, it will point to 0 always. we need to advance optindex for every option.Novotny
I
3

I recently came across this issue myself. I arrived at a similar solution to the one Brian Vandenberg and Haystack suggested. But to improve readability and avoid code duplication, you can wrap it all up in a macro like below:

#define OPTIONAL_ARGUMENT_IS_PRESENT \
    ((optarg == NULL && optind < argc && argv[optind][0] != '-') \
     ? (bool) (optarg = argv[optind++]) \
     : (optarg != NULL))

The macro can be used like this:

case 'o': // option with optional argument
    if (OPTIONAL_ARGUMENT_IS_PRESENT)
    {
        // Handle is present
    }
    else
    {
        // Handle is not present
    }
    break;

If you are interested, you can read more about how this solution works in a blog post I wrote: https://cfengine.com/blog/2021/optional-arguments-with-getopt-long/

This solution is tested and is – at the time of this writing – currently used in CFEngine.

Iridescence answered 14/9, 2021 at 11:42 Comment(0)
M
1

I also ran into the same problem and came here. Then I realised this . You don't have much of a use case of "optional_argument" . If an option is required you check from program logic, if an option is optional then you need not do anything because at getopt level all options are optional , they are not mandatory, so there is no use case of "optional_argument". Hope this helps.

ps: for the above example i think the correct options are --praise --praise-name "name" --blame --blame-name "name"

Moluccas answered 6/9, 2015 at 21:46 Comment(1)
You don't have much of a use case of "optional_argument" -- Oh, but we do. Imagine that we have $ whip --spank user5081924 and $ whip --spank. The first spanks a specific user, the second spanks the caller. Without the optional argument you'd be forced to either only spank yourself or only spank others. (Could have a --spank-self and --spank-other I suppose, but why complicate.)Sochor

© 2022 - 2024 — McMap. All rights reserved.