![]() |
|
Tutorial CLI parsing in C, part 1 - 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 1 (/Thread-Tutorial-CLI-parsing-in-C-part-1) |
CLI parsing in C, part 1 - Inori - 07-13-2017 In any language, parsing CLI options (the arguments prefixed with - or --) is a chore. Thankfully, the getopt library exists for C and C++. Being C, it won't do things like generate usage strings or help messages for you as other languages like Python might, but it mitigates many of the annoyances that come with this task. Basic structure
To use the basic getopt (which only supports short flags, i.e. "-" followed by one character), we need to declare an int (in our case, "c") and assign it the return value of the getopt function, which takes an int, argc; a char**, argv; and a char*, options which I'll explain later (for any nitpickers, yes, I'm simplifying) until it returns -1.Code: #include <getopt.h> // getopt library header
int main(int argc,char **argv){
int c;
while((c=getopt(argc,argv,"ab:c::"))!=-1){
...
}
}Options
Next, the options argument that I said I would explain. Each option is made up of a non-colon character (representing the option's identifier, e.g. "x" in options would match "-x" when the program is executed); followed by nothing, one colon to take an argument (e.g. "x:"), or two colons for an optional argument (e.g. "x::"; this is a GNU extension, there's nothing in POSIX RFC that requires it. It actually breaks the standard that optional arguments shouldn't exist).To match options, it's common practice to switch on the return value of getopt within the while loop. Storing option results needs to be done manually. If you're going to be passing the options to other functions, a struct works nicely. Code: #include <getopt.h>
struct opt_results{
int a,fc;
char *b,*sc;
} opt_results;
int main(int argc,char **argv){
struct opt_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; // argument passed to -b
break;
case 'c':
res.sc=optarg; // argument passed to -c (or null)
res.fc=1;
break;
}
}
}Getopt can also return '?' for unknown options or erroneous arguments. If the "opterr" variable is set to 0 (default), getopt will print these messages automatically, but it's a good example nonetheless. Code: #include <getopt.h>
#include <stdlib.h> // needed for abort
#include <stdio.h> // needed for fprintf
int main(int argc,char **argv){
int c;
opterr=1; // disable automatic error reporting
while((c=getopt(argc,argv,"ab:c::"))!=-1){
switch(c){
...
case '?':
if(optopt=='c') // optopt is the erroneous option
fprintf(stderr,"Option -%c requires an argument.\n",optopt);
else if(isprint(optopt)) // optopt is printable
fprintf(stderr,"Unknown option `-%c'.\n",optopt);
else // optopt is a non-printable byte
fprintf(stderr,"Unknown option character `\\x%x'.\n",optopt);
return 1;
default: // something went horribly wrong
abort();
}
}
}Non-option arguments
The last new thing we'll be exploring in this part is non-option arguments. If your program takes extra arguments after the options (e.g. a list of files), you can use the "optind" variable set by getopt to get the remaining arguments.Code: int main(int argc,char **argv){
int c;
while((c=getopt(argc,argv,"ab:c::"))!=-1){
...
}
printf("files:");
for(int i=optind;i<argc;i++)
printf(" %s",argv[i]);
printf("\n");
}Putting it all together
At the end of it all, you'll have a working parser! I made the functional one below using the examples throughout this tutorial.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;
opterr=1;
while((c=getopt(argc,argv,"ab:c::"))!=-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 '?':
if(optopt=='c') // optopt is the erroneous option
fprintf(stderr,"Option -%c requires an argument.\n",optopt);
else if(isprint(optopt)) // optopt is printable
fprintf(stderr,"Unknown option `-%c'.\n",optopt);
else // optopt is a non-printable byte
fprintf(stderr,"Unknown option character `\\x%x'.\n",optopt);
return 1;
default: // something went horribly wrong
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");
}A quick end note: this is part 1 of 2. The getopt_long function (part of the same library) is more complicated, so I thought I'd lay the foundation here first. RE: CLI parsing in C, part 1 - BORW3 - 07-16-2017 LOL, just boost that stuff, way easier. RE: CLI parsing in C, part 1 - Inori - 07-16-2017 (07-16-2017, 08:48 PM)BORW3 Wrote: LOL, just boost that stuff, way easier. Boost is a terrible solution if you're trying to make anything lightweight. Plus, how do you learn C if you use a library that does everything for you? |