Search code examples
cglibcgetoptunistd.h

C's getopt not able to parse options at the end (or in between) of argv


I love getopt to parse options from the argv argument. Unfortunately I'm not able to make getopt parse options that are between nonoptions or at the end of argv. Example Program:

#include <stdio.h>
#include <unistd.h>

int o_help = 0;

int main(int argc, char *argv[]) {
    int opt, i;
    while ((opt = getopt(argc, argv, "h")) != -1) {
        switch (opt) {
            case 'h':
                o_help = 1;
                break;
            default:
                return -1;
        }
    }

    printf("o_help=%d\n", o_help);
    for (i = 1; i < argc; i++) {
        printf("%s ", argv[i]);
    }
    printf("\n");
    return 0;
}

Running the program in an alpine container on macOS Mojave, I get the following outputs:

$ gcc prog.c
$ ./a.out hello -h world
o_help=0
hello -h world
$ ./a.out -h hello world
o_help=1
-h hello world

The official getopt man page states:

By default, getopt() permutes the contents of argv as it scans, so that eventually all the nonoptions are at the end.

Since I did not specify otherwise, I expect the default behaviour to 1. parse the -h option and 2. permute argv so that the nonoptions are at the end of the array. Unfortunately, calling the program like ./a.out hello -h world does neither parse -h nor permute the array. Help would be greatly appreciated.


Solution

  • Marco's answer is correct, but "install glibc by yourself" is really not a good suggestion if you're not using a glibc-based distribution; you'd be building a whole parallel library ecosystem by yourself. There are a couple canonical solutions to your problem that are much easier.

    The GNU-endorsed method of using nonstandard GNU behaviors like the one you want is to use gnulib and autoconf, which can automatically replace getopt with a GNU version as needed. However that's a heavy change and it requires your program to be GPL'd.

    A much easier solution is just using getopt_long with a degenerate list of long options, instead of getopt. Since getopt_long is not a standard-governed function but an extension originally define on GNU, musl's implementation of it is free to follow the GNU behavior of permuting argv to allow mixed options and non-option arguments, and does so.