tags:

views:

572

answers:

4

I have a program which takes various command line arguments. For the sake of simplification, we will say it takes 3 flags:

-a, -b, and -c

and use the following code to parse my arguments

    int c;
    while((c =  getopt(argc, argv, ":a:b:c")) != EOF)
    {
        switch (c)
        {
             case 'a':
                 cout << optarg << endl;
                 break;
             case 'b':
                 cout << optarg << endl;
                 break;
             case ':':
                 cerr << "Missing option." << endl;
                 exit(1);
                 break;
        }
    }

note: a, and b take parameters after the flag

But I run into an issue if I invoke my program say with

./myprog -a -b parameterForB

where I forgot parameterForA, the parameterForA (represented by optarg) is returned as

-b

and parameterForB is considered an option with no parameter and optind is set to the index of parameterForB in argv

The desired behavior in this situation would be that ':' is returned after no argument is found for -a, and "Missing option." is printed to standard error. However, that only occurs in the event that -a is the last parameter passed into the program.

I guess the question is: is there a way to make getopt() assume that no options will begin with '-'?

+1  A: 
e.James
The ':' at the beginning of my string in the call to getopt() tells it to return a ':' in the event that a known parameter requires an argument. Also, I stated that "that <the desired behavior> occurs in the event that -a is the last parameter passed into the program." Which is exactly the case in your example.
segfault
Yes, you did specify that in your question. My fault for not paying close enough attention. Anyway, I posted an amendment with a short `if` statement that may provide the behaviour you are looking for. Best of luck!
e.James
thats seems like a good enough fix. thanks for the help.
segfault
@segfault: note that this code produces undefined results for options that don't take arguments. The implementation can advance `optarg` to the next `argv` entry because `optarg` is undefined if not used.
Potatoswatter
@Potatoswatter: That sounds ominous. Are you saying the call to `optarg[0]` is undefined? `optarg` *must* be declared somewhere, otherwise the code wouldn't compile. Should the if statement simply check for `(optarg != NULL)` before reading `optarg[0]`?
e.James
@James: see the link in my answer for all the guarantees on `optarg`. I wouldn't count on `optarg[0]` working if `optarg` has no defined value. See my code for what I think would be the best workaround.
Potatoswatter
That looks much safer. Nice work.@segfault: I would change the accepted answer to Potatoswatter's.
e.James
Yes, that ended up being the case. I added the check for if != null last night
segfault
+1  A: 

There are quite a few different versions of getopt around, so even if you can get it to work for one version, there will probably be at least five others for which your workaround will break. Unless you have an overwhelming reason to use getopt, I'd consider something else, such as Boost.Program_options.

Jerry Coffin
+4  A: 

If you are working in C++, boost::program_option is my recommendation to parse command line argument:

Phong
I am not. I need to use getopt
segfault
+2  A: 

See the POSIX standard definition for getopt. It says that

If it [getopt] detects a missing option-argument, it shall return the colon character ( ':' ) if the first character of optstring was a colon, or a question-mark character ( '?' ) otherwise.

As for that detection,

  1. If the option was the last character in the string pointed to by an element of argv, then optarg shall contain the next element of argv, and optind shall be incremented by 2. If the resulting value of optind is greater than argc, this indicates a missing option-argument, and getopt() shall return an error indication.
  2. Otherwise, optarg shall point to the string following the option character in that element of argv, and optind shall be incremented by 1.

It looks like getopt is defined not to do what you want, so you have to implement the check yourself. Fortunately, you can do that by inspecting *optarg and changing optind yourself.

int c, prev_ind;
while(prev_ind = optind, (c =  getopt(argc, argv, ":a:b:c")) != EOF)
{
    if ( optind == prev_ind + 2 && *optarg == '-' ) {
        c = ':';
        -- optind;
    }
    switch ( …
Potatoswatter
This has the added bonus of actually allowing arguments that start with `-` — they just can't be preceded by whitespace. (eg `my_prog -x-a`)
Potatoswatter