getopt does not parse optional arguments to parameters
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 JohnKudos to John$ ./respond --praise=JohnKudos to John$ ./respond --blame JohnYou suck !$ ./respond --blame=JohnYou suck , John!
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, updatingoptind
andnextchar
so that the next call togetopt
can resume the scan with the following option character or ARGV-element.If there are no more option characters,
getopt
returns -1. Thenoptind
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 inoptarg
, otherwiseoptarg
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;}
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.