Reputation: 3108
I'm making a small program in C that deals with a lot of command line arguments, so I decided to use getopt to sort them for me.
However, I want two non-option arguments (source and destination files) to be mandatory, so you have to have them as arguments while calling the program, even if there's no flags or other arguments.
Here's a simplified version of what I have to handle the arguments with flags:
while ((c = getopt(argc, argv, "i:d:btw:h:s:")) != -1) {
switch (c) {
case 'i': {
i = (int)atol(optarg);
}
case 'd': {
d = (int)atol(optarg);
}
case 'b':
buf = 1;
break;
case 't':
time = 1;
break;
case 'w':
w = (int)atol(optarg);
break;
case 'h':
h = (int)atol(optarg);
break;
case 's':
s = (int)atol(optarg);
break;
default:
break;
}
}
How do I edit this so that non-option arguments are also handled?
I also want to be able to have the non-options either before or after the options, so how would that be handled?
Upvotes: 25
Views: 49256
Reputation: 1
As per Langson.Leung's answer:
According to https://www.man7.org/linux/man-pages/man3/getopt.3.html
By default, getopt() permutes the contents of argv as it scans, so that eventually all the nonoptions are at the end. Two other scanning modes are also implemented. If the first character of optstring is '+' or the environment variable POSIXLY_CORRECT is set, then option processing stops as soon as a nonoption argument is encountered. If the first character of optstring is '-', then each nonoption argv-element is handled as if it were the argument of an option with character code 1. (This is used by programs that were written to expect options and other argv-elements in any order and that care about the ordering of the two.) The special argument "--" forces an end of option-scanning regardless of the scanning mode.
The contents of argv
might indeed be permuted, but this is a GNU extension, technically not "POSIXLY_CORRECT", and:
the specific way in which they are permuted is (probably) implementation-dependent. It appears that in the GNU/Linux case, this is done gradually, with non-option arguments "bubbling" up towards the end of argv
; every time getopt()
finds an option argument, it makes sure that all non-option arguments encountered thus far are placed exactly before the option argument found -- try printing the contents of argv
after each getopt()
invocation inside the loop and see what you get.
generally, getopt()
will skip over any arguments that do not match with the options provided. It will identify the rest correctly, wherever inside the argv
they reside, and will return -1
when all arguments have been scanned (i.e. the end of argv
is reached), with optind
set to point at the first non-option argument, all of which are grouped together towards the end of argv
in the order they were encountered. You can then process the non-option arguments after the loop, as already stated in previous answers.
If you want to process the non-option arguments on-the-fly, you would do something like this (see the comments in the code below!):
const char *src_file = NULL;
const char *dest_file = NULL;
/* notice the '-' character in front of the opstring arg to getopt() */
while ((c = getopt(argc, argv, "-i:d:btw:h:s:")) != -1) {
switch (c) {
/* parsed an argument that isn't among the
provided options, getopt() returns 1 */
case 1:
/* do whatever needs to be done with the non-option argument;
notice that optind is already incremented by getopt(),
so we access argv @index optind-1 */
printf("argv[%d]=%s\n", optind-1, argv[optind-1]);
/* alternatively, since this non-option argument is treated
as if it were an argument to an option with value 1,
we can refer to it via optarg! */
printf("%s\n", optarg);
/* the following assumes the first non-option argument encountered
to be the source file and the second non-option argument to be
the destination file; all (if any) subsequent non-option
arguments are ignored */
if (src_file == NULL)
src_file = optarg;
else if (dest_file == NULL)
dest_file = optarg;
else
printf("src and dest files already set, arg ignored!\n");
break;
case 'i': {
i = (int)atol(optarg);
}
case 'd': {
d = (int)atol(optarg);
}
case 'b':
buf = 1;
break;
case 't':
time = 1;
break;
case 'w':
w = (int)atol(optarg);
break;
case 'h':
h = (int)atol(optarg);
break;
case 's':
s = (int)atol(optarg);
break;
default:
break;
}
}
And then after the while loop, check if everything was provided:
if ( src_file == NULL || dest_file == NULL ) {
printf("Mandatory argument(s) missing!\n");
print_help();
exit(1);
}
Explanation: By putting '-'
in front of the opstring
arg, we allow getopt()
to scan the argv
provided from left to right (no rearrangements performed) and return a positive integer value for each and every argument encountered, so we can handle them appropriately:
'-'
character but do not match any of the options provided are treated as unknown options and a message of the form <program_name>: invalid option -- '<character>'
is implicitly printed by getopt()
; they return the value of the character '?'
, so if we want special handling for them, we can add a case '?':
to catch them :)1
, so we can catch them with case 1:
and handle them whichever way we please (e.g. on-the-fly as above, or we may put them in a separate buffer to process them later, outside of the getopt()
loop etc)getopt()
returns -1
, and we exit the loop.Alternatively: Compare with prefixing the opstring
arg to getopt()
with '+'
(see manpage at top), where getopt()
would return -1
at the first non-option argument encountered. We would exit the loop (possibly) early, but nothing can stop us from processing the offending argument, advancing the optind
variable manually, and then resuming scanning from where it stopped!
while (optind < argc) {
while ( (c = getopt("+...")) != -1 ) {
...
}
if (optind < argc) {
printf("early -1 returned: optind=%d\n", optind);
/* note that we cannot access the argument through the optarg
variable in this case -- makes sense, it's not that we
treat a non-option argument as if it were an argument to
an option with value 1, as is the case with '-' mode
scanning ;) */
printf("argv[%d]=%s\n", optind, argv[optind]);
/* handle argument */
...
optind++;
}
}
Portability: This is supposedly the "POSIXLY_CORRECT" way to parse the argument vector, so I would assume it works as expected in every POSIX-conforming environment, although I have no way of testing it.
Remember to remove the '+'
prefix to the optstring
arg, since this is a GNU extension:
The use of '+' and '-' in optstring is a GNU extension.
as per the manpage. If you are in a GNU environment, set the POSIXLY_CORRECT environment variable instead:
export POSIXLY_CORRECT=1
or simply run your program like so:
POSIXLY_CORRECT=1 ./program_name <argv>
Upvotes: 0
Reputation: 33273
getopt
sets the optind
variable to indicate the position of the next argument.
Add code similar to this after the options loop:
if (argv[optind] == NULL || argv[optind + 1] == NULL) {
printf("Mandatory argument(s) missing\n");
exit(1);
}
Edit:
If you want to allow options after regular arguments you can do something similar to this:
while (optind < argc) {
if ((c = getopt(argc, argv, "i:d:btw:h:s:")) != -1) {
// Option argument
switch (c) {
case 'i':
i = (int)atol(optarg);
break;
case 'd':
d = (int)atol(optarg);
break;
case 'b':
buf = 1;
break;
case 't':
time = 1;
break;
case 'w':
w = (int)atol(optarg);
break;
case 'h':
h = (int)atol(optarg);
break;
case 's':
s = (int)atol(optarg);
break;
default:
// bad or unknown option
show_help();
exit(1);
break;
}
} else {
// Regular argument
<code to handle the argument>
optind++; // Skip to the next argument
}
}
Upvotes: 38
Reputation: 3650
I'm going to go ahead and throw my answer in. I've tested it, and it complies with getopt
as described here on Linux. Note, this answer only allows options first, then non-options after. But this is normal for a lot of CLI tools.
I'm adding this answer now because I found the EDIT in the answer from Klas did not work.
#include <stdio.h>
#include <stdlib.h>
#include <getopt.h>
#include <ctype.h>
int main (int argc, char * const argv[])
{
int c;
// Process option arguments
while (-1 != (c = getopt(argc, argv, "abc:"))) {
switch (c) {
case 'a': printf("option: a\n"); break;
case 'b': printf("option: b\n"); break;
case 'c': printf("option: c with arg: \"%s\"\n", optarg); break;
default:
printf("unknown arg: %2X\n", optopt);
}
}
// Process remaining arguments
for (int i = optind; i < argc; ++i) {
printf("non-option: %s\n", argv[i]);
}
return EXIT_SUCCESS;
}
Upvotes: 0
Reputation: 1
The author of Mead's Guide to getopt states
If you want to have getopt parse and return the non-option arguments in the while loop (in the order specified), you must direct it to do so by putting a minus (-) in front of the optstring.
The example provided was "-:a:b:X" where the minus (-) "disables getopt from moving all non-option arguments to the end of the command line" and the colon (:) "disables getopt from displaying error messages".
If a non-option argument is found, then getopt will return an integer value of 1.
Upvotes: 0
Reputation: 5181
int main(int argc, char** argv) {
char* inputfile;
char* outputfile;
char* output_file_type;
char* color_red;
char* color_blue;
char* color_green;
int opt;
if (argv[optind] == NULL || argv[optind + 1] == NULL) {
printf("Mandatory argument(s) missing\n");
exit(1);
}
while((opt = getopt(argc, argv, ":i:o:r:g:b:t:")) != -1){
switch(opt){
case 'i':
inputfile = optarg;
printf("Input file : %s\n",inputfile);
break;
case 'o':
outputfile = optarg;
printf("Output File: %s\n",outputfile);
break;
case 't':
output_file_type = optarg;
printf("Output File type: %s\n", output_file_type);
break;
case 'r':
color_red = optarg;
printf("Color Red: %s\n",color_red);
break;
case 'g':
color_green = optarg;
printf("Color Green: %s\n",color_green);
break;
case 'b':
color_blue = optarg;
printf("Color Blue: %s\n",color_blue);
break;
case ':':
printf("option needs a value\n");
break;
case '?':
printf("unknown option: %c\n", optopt);
break;
}
}
for (; optind < argc; optind++){
printf("Given extra arguments: %s\n", argv[optind]);
}
return (EXIT_SUCCESS);
}
Run commands:
gcc main.c -o image
./image -i ./resource/input_file.bmp -o ./resource/output_file.bmp -t BPM -r 10 -g 24 -b 40
output:
Input file : ./resource/input_file.bmp
Output File: ./resource/output_file.bmp
Output File type: BPM
Color Red: 10
Color Green: 24
Upvotes: 1
Reputation: 29
According to https://www.man7.org/linux/man-pages/man3/getopt.3.html
By default, getopt() permutes the contents of argv as it scans, so that eventually all the nonoptions are at the end. Two other scanning modes are also implemented. If the first character of optstring is '+' or the environment variable POSIXLY_CORRECT is set, then option processing stops as soon as a nonoption argument is encountered. If the first character of optstring is '-', then each nonoption argv-element is handled as if it were the argument of an option with character code 1. (This is used by programs that were written to expect options and other argv-elements in any order and that care about the ordering of the two.) The special argument "--" forces an end of option-scanning regardless of the scanning mode.
Upvotes: 2
Reputation: 1017
Really good example could be found here: GNU Libc The code:
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int
main (int argc, char **argv)
{
int aflag = 0;
int bflag = 0;
char *cvalue = NULL;
int index;
int c;
opterr = 0;
while ((c = getopt (argc, argv, "abc:")) != -1)
switch (c)
{
case 'a':
aflag = 1;
break;
case 'b':
bflag = 1;
break;
case 'c':
cvalue = optarg;
break;
case '?':
if (optopt == 'c')
fprintf (stderr, "Option -%c requires an argument.\n", optopt);
else if (isprint (optopt))
fprintf (stderr, "Unknown option `-%c'.\n", optopt);
else
fprintf (stderr,
"Unknown option character `\\x%x'.\n",
optopt);
return 1;
default:
abort ();
}
printf ("aflag = %d, bflag = %d, cvalue = %s\n",
aflag, bflag, cvalue);
for (index = optind; index < argc; index++)
printf ("Non-option argument %s\n", argv[index]);
return 0;
}
It allows to have options before and after arguments. I did compile and run test example:
$ ./a.out aa ff bb -a -ctestparam hello
aflag = 1, bflag = 0, cvalue = testparam
Non-option argument aa
Non-option argument ff
Non-option argument bb
Non-option argument hello
Upvotes: 17