2

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.

  • Can't reproduce (With Ubuntu). Do you perhaps have the `POSIXLY_CORRECT` environment variable set? With the glibc version of `getopt()`, that would give the results you're seeing. – Shawn Feb 09 '20 at 14:15
  • (I'm assuming that *alpine container* refers to a Alpine Linux VM. If it's something else and you're using the OS X version of `getopt()` but looking at the glibc documentation...) – Shawn Feb 09 '20 at 14:23
  • 1
    Actually, looks like Alpine uses musl, not glibc. So, yeah. Sure you're looking at the right documentation? – Shawn Feb 09 '20 at 14:24
  • @Shawn I'm working on a docker [alpine linux container](https://hub.docker.com/_/alpine) which runs as a process on OS X. I installed standard library using `apk add libc-dev`, I'm not quite sure whether this is the `glibc` implementation. But you're probably right, for example the OS X man page on [getopt](https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man3/getopt.3.html) doesn't provide such a behaviour: "When all options have been processed (i.e., up to the first non-option argument), getopt() returns -1" – Mike Nöthiger Feb 09 '20 at 14:53

2 Answers2

1

You're looking at the manual page for the glibc version of getopt(). Alpine Linux does not use glibc, it uses musl libc.

Looking at differences between the two, from this page of the musl libc wiki you can read:

GNU getopt permutes argv to pull options to the front, ahead of non-option arguments. musl and the POSIX standard getopt stop processing options at the first non-option argument with no permutation.

So, the solution here is:

  1. Use musl, but permute argv by yourself before calling getopt().
  2. Install glibc by yourself or use an already made Docker image.
Marco Bonelli
  • 63,369
  • 21
  • 118
  • 128
  • "Install glibc by yourself" is really not a good suggestion if you're not using a glibc-based distribution; you're going to be building a whole parallel library ecosystem by yourself. I'll post a better alternative. – R.. GitHub STOP HELPING ICE Feb 09 '20 at 16:22
  • @R..GitHubSTOPHELPINGICE you're not going to build anything by yourself, it's already packaged and installable in a second. – Marco Bonelli Feb 09 '20 at 16:23
  • OP is going to need to build whatever software they're writing/building, which likely depends on an ecosystem of libraries not just libc. Moreover Apline does not ship a glibc cross compiler for compiling against glibc, so they'd have to setup that too. – R.. GitHub STOP HELPING ICE Feb 09 '20 at 16:27
1

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.

R.. GitHub STOP HELPING ICE
  • 208,859
  • 35
  • 376
  • 711
  • Thank you very much, I never checked the documentation of `getopt_long` - it's awesome and does in fact also permute argv in the `musl` implementation, which I've just checked. – Mike Nöthiger Feb 09 '20 at 19:43