Sinisterly
Tutorial CLI parsing in C, part 2 - Printable Version

+- Sinisterly (https://sinister.ly)
+-- Forum: Coding (https://sinister.ly/Forum-Coding)
+--- Forum: C, C++, & Obj-C (https://sinister.ly/Forum-C-C-Obj-C)
+--- Thread: Tutorial CLI parsing in C, part 2 (/Thread-Tutorial-CLI-parsing-in-C-part-2)



CLI parsing in C, part 2 - Inori - 07-31-2017

Now that we've gone over how to use getopt, understanding its extended version, getopt_long, shouldn't be too difficult.

Redefining options
The function we're looking for, getopt_long, is in the same header as its counterpart: getopt.h. The tips in the GNU manual recommend adding long options to any program that uses options at all, since it makes the things they represent more explicit and easier to understand.

First, let's take a look at the parser we constructed in the last part, using the buildin error handling:
Code:
#include <getopt.h>
#include <stdlib.h>
#include <stdio.h>

struct opts_results{
    int a,fc;
    char *b,*sc;
} opts_results;

int main(int argc,char **argv){
    struct opts_results res;
    int c;

    while((c=getopt(argc,argv,"ab:c::"))!=-1){
        switch(c){
            case 'a':
                res.a=1;
                break;
            case 'b':
                res.b=optarg;
                break;
            case 'c':
                res.sc=optarg;
                res.fc=1;
                break;
            case '?':
                break;
            default:
                abort();
        }
    }
    printf("a: %d, b: %s, fc: %d, sc: %s\n",res.a,res.b,res.fc,res.sc);

    printf("files:");
    for(int i=optind;i<argc;i++)
        printf(" %s",argv[i]);
    printf("\n");
}

To convert this to a working getopt_long parser, we first need to define our long options. We do this using an array of "option" structs, defined by the header. The option struct has four fields.
const char *name: the long name of the option without the prefix (--).
int has_arg: an indicator of whether or not the option takes an argument--no_argument (equivalent to no colon suffix) and required_argument (equivalent to a one colon suffix) are self-explanatory, but use optional_argument (two colons) for the GNU extension.
int *flag and int val: these control how to handle the option when it appears. If flag is a null pointer, val is used to identify the option (the great thing about the char type is that we can use them as ints here and just alias the short options). If flag isn't a null pointer, it should point to an int variable, and val should be the value assigned to the variable flag points to when this option is found.

When getopt_long sets a flag, it always returns 0. This will be important in a little bit.

Lastly for options, the array must end with a completely null option. Given these parameters, we can create a set for our previous parser.
Code:
struct option opts[]={
    {"a-flag",no_argument,&res.a,1}, // no bound short option, but we can still define one if we want
    {"b-with-arg",required_argument,0,'b'}, // alias to the '-b' short option
    {"c-gnu-extension",optional_argument,0,'c'}, // uses GNU extension, alias to '-c'
    {0,0,0,0} // required null option
};

Using the function
The getopt_long function is similar to the short version, but with a few extras. If we compare the two function's signatures, we can see that getopt_long requires our option array and an int* on top of what's needed for getopt.
Code:
int getopt_long(int argc, char *const *argv, const char *shortopts, const struct option *longopts, int *indexptr)
int getopt(int argc, char *const *argv, const char *options)

The longopts argument is easy, just pass it the array we defined before. int *indexptr stores the index of any found long options. We can get the name using "longopts[*indexptr].name", utilizing the option struct, but we only have one so there's not much reason to bother. In our case, we can make it a null pointer.

When all is said and done, our new call should look something like this:
Code:
while((c=getopt_long(argc,argv,"ab:c::",opts,0))!=-1)
    ...

Working example
Like the last part, a full, working example to tinker with is available below.
Code:
#include <getopt.h>
#include <stdlib.h>
#include <stdio.h>

struct opts_results{
    int a,fc;
    char *b,*sc;
} opts_results;

int main(int argc,char **argv){
    struct opts_results res;
    int c;

    struct option opts[]={
        {"a-flag",no_argument,&res.a,1},
        {"b-with-arg",required_argument,0,'b'},
        {"c-gnu-extension",optional_argument,0,'c'},
        {0,0,0,0}
    };

    while((c=getopt_long(argc,argv,"ab:c::",opts,0))!=-1){
        switch(c){
            case 'a':
                res.a=1;
                break;
            case 'b':
                res.b=optarg; // argument passed to -b
                break;
            case 'c':
                res.sc=optarg; // argument passed to -c (or null)
                res.fc=1;
                break;
            case '?':
                break;
            default:
                abort();
        }
   }
   printf("a: %d, b: %s, fc: %d, sc: %s\n",res.a,res.b,res.fc,res.sc);

   printf("files:");
   for(int i=optind;i<argc;i++)
       printf(" %s",argv[i]);
   printf("\n");
}



RE: CLI parsing in C, part 2 - titbang - 08-11-2017

I have never used getopt(). I always push the arguments into a vector and process that. Thanks for the share.