Dealing with multiple program arguments, but only one is allowed in C - c

I have a problem figuring out how to reasonably consider which of the available arguments was given in a situation where one can be used, but there is also an optional argument after which one of the mandatory ones can be given. It is specifically about getopt in C. Example: I have a program with the argument -a -b -c and an optional -d. Only one of the first three may be used. Normally I would do something like that:
while ((opt = getopt(argc, argv, OPTSTR)) != EOF)
{
switch (opt)
{
case 'd':
optional_flag = 1;
break;
case 'a':
if (!mandatory_flag)
mandatory_flag = 1;
else
usage_prompt(argv[0]);
break;
case 'b':
if (!mandatory_flag)
mandatory_flag = 1;
else
usage_prompt(argv[0]);
break;
case 'c':
if (!mandatory_flag)
mandatory_flag = 1;
else
usage_prompt(argv[0]);
break;
The problem now is that I'm just checking to see if an optional argument was given and that if many mandatory arguments were not given. I could use a separate flag for each option, but it would be tiresome for many parameters. Is there any better solution to keep the logic but also know which parameter was given? I am thinking about another switch where I will be able to run appropriate functions.

Related

How to get the arguments from getopt to work together

I want to be able to use every argument (-S, -s, -f) and them be able to be used together. -S prints the files in the folder and their size... -s prints the files if they are >= the file size provided by the argument -f finds all the files with the given substring.
How would I get these to work together? Right now, my code does all of this separately.
while((c = getopt(argc, argv, "Ss:f:")) != -1){
switch(c){
case 'S':
// No clue how to make them work together.
printf("Case: S\n");
printf("option -%c with argument '%s'\n", c, argv[optind]);
printDIR(cwd, case_S);
break;
case 's':
printf("Case: s\n");
printf("option -%c with argument '%s'\n", c, optarg);
printDIR(cwd, case_s);
break;
case 'f':
printf("Case: f\n");
printf("option -%c with argument '%s'\n", c, optarg);
printDIR(cwd, case_f);
break;
default:
printf("...");
}
}
printDIR is a pointer function which is why I have cwd(which is the directory) and case_S and so on.
I want to be able to say... './search -S -s 1024 -f tar'. This should recursively search the current directory and print the size of the file if it is >= 1024 and if the file has the substring 'tar' in it. But I also want it to work even if I don't provide all arguments.
This is my first time trying anything like this so I'm new to trying to make UNIX commands and using getopt args.
Converting parts of some of my comments into an answer.
You should process the options without doing any actions. Only when you've finished processing the options, with no errors, do you think about doing anything like calling printDIR(). You'll probably need more arguments to the function, or use global variables.
You'd have a flag such as:
int recursive = 0;
which you would set to 1 if the search was to be recursive. And int minimum_size = 0; and modify it with -s 1024. And const char *filter = ""; and then modify that with -s tar. Etc. Often, these are global variables — but if you can avoid that by passing them to the function, that is better.
Your function might then become:
int printDIR(const char *cwd, int recursive, int minimum, const char *filter);
and you'd call it with the appropriately set local variables. Note that you should check the conversion from string to integer before calling printDIR().
If there are non-option arguments, you'd process them after the option handling with:
for (int i = optind; i < argc; i++)
printDIR(argv[i], recursive, minimum_size, filter);

Switch case not affecting variable C

int main (int argc, char **argv) {
//Initialise variable for switch option
int option = 0;
//Set time to default to epoch
char *argtime = "1970-01-01 00:00:00";
//Use getopt to parse switches t and h (neither requires an argument, as epoch is defaulted to if non is supplied)
while ((option = getopt(argc, argv, "th")) != -1) {
//Switch statements for supplied switches
switch (option) {
//If -t
case 't':
//If arg supplied
if(optarg != NULL) {
//Set arg to given arg
argtime = optarg;
}
printf("-t is selected.\n");
break;
//If -h
case 'h':
printf("Help is selected\n");
break;
//If anything else
default:
printf("Option invalid\n");
return 1;
}
}
printf("The set time is %s\n", argtime);
return 0;
}
Here's some code I wrote to use getopt() to parse a command line switch, and argument. I want it argtime to default to "1970-01-01 00:00:00" if no argument is supplied, which it does, but when I use the -t switch and supply a different argument, argtime remains unaffected. So basically argtime = optarg; is not doing what it should, but I don't know why.
You need to tell getopt() about options that require an argument. As #xing described in comments, you do that by putting a colon in the option string, after the option letter that is affected. This allows getopt() to correctly handle grouped options and to recognize non-option arguments. Only if you do this can you expect option arguments to be communicated to you via optarg.
You claim in comments to want an optional option argument. POSIX-standard getopt() does not provide for that. In your particular case, it's not clear why you even want it, for what would it mean to specify a -t option without an argument? That the program should use a default? But that's what it will do if -t is omitted altogether.
Nevertheless, the way to approach the situation with POSIX getopt() is to provide two separate options, one that takes an argument and one that doesn't. For example, you might use option -T to signify whatever you wanted -t without an option to mean (getopt(argc, argv, "t:Th")). Alternatively, if you are willing to rely on GNU extensions then you can signify an optional option argument via a double colon (getopt(argc, argv, "t::h")), but this is less portable, and has slightly different semantics.
You might use optind after the while to see if there is another argument and if so, use that argument instead of the default.
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
int main (int argc, char **argv) {
//Initialise variable for switch option
int option = 0;
//Set time to default to epoch
char *argtime = "1970-01-01 00:00:00";
//Use getopt to parse switches t and h (neither requires an argument, as epoch is defaulted to if non is supplied)
while ((option = getopt(argc, argv, "th")) != -1) {
//Switch statements for supplied switches
switch (option) {
//If -t
case 't':
//If arg supplied
printf("-t is selected.\n");
break;
//If -h
case 'h':
printf("Help is selected\n");
break;
//If anything else
default:
printf("Option invalid\n");
return 1;
}
}
if ( optind < argc) {//more arguments available
//Set arg to given arg
argtime = argv[optind];
}
printf("The set time is %s\n", argtime);
return 0;
}

how to determine order and count of arguments in getopt

I use getopt to get arguments from terminal, but it depends me on the order of arguemts. there's a difference between
./my -A -B -C
and
./my -B -A -C
is there any way to determine the order?
secondly, I'd like to determine count of the args (if there are some optional ones)
ps sorry for my english, i hope you understand what i mean
you have argv[] as a parameter passed to main() it contains an array of pointers to character strings. Those strings are in the exact order they were on the command line. The way to determine if some argument was listed before another.
is to step through the argv[] strings.
You record the order in which getopt() returns them. It returns them in left-to-right order, so there's no mystery. In the first invocation, -A is reported before -B and -C is reported last. In the second, -B is reported before -A and -C is still last. You can decide to do the processing when the arguments are read (so the actions occur while you're reading the options), or you can store the options in the sequence they appear and do the processing after the options are completely read. Both are feasible. If an error occurs with the first technique, though, the early processing will have been done. If that's unacceptable, process all the options first.
You can count the number of option arguments in your loop easily enough:
int optcnt = 0;
int opt;
while ((opt = getopt(argc, argv, "ABC")) != -1)
optcnt++;
You'd have a loop within braces with a switch as well as the simple increment, probably.
Note that you can have repeated options, possibly with a different option value on each time:
./my -D name1 -D name2 -A -B -D name3 -C
You can keep a record of which options and which names appear in sequence.
int optcnt = 0;
int opt;
int o_idx = 0;
int d_idx = 0;
char *d_opts[20];
char o_opts[20];
while ((opt = getopt(argc, argv, "ABCD:")) == -1)
{
optcnt++;
switch (opt)
{
case 'A':
case 'B':
case 'C':
o_opts[o_idx++] = opt;
break;
case 'D':
o_opts[o_idx++] = opt;
d_opts[d_idx++] = optarg;
break;
default:
…report error and exit…
break;
}
}
/* Parsing complete - do final checks */
/* Processing loop */
int d = 0;
int i;
for (i = 0; i < o_idx; i++)
{
switch (o_opts[i])
{
case 'A':
process_A();
break;
case 'B':
process_B();
break;
case 'C':
process_C();
break;
case 'D':
process_D(d_opts[d++]);
break;
}
}
assert(d == d_idx && i == o_idx);
Resetting getopt() back to the beginning is an undocumented art; try not to need to reparse your command line.

C, How is getopt() updating

I'm trying to make sense of a section of skeleton code for a class. The intended usage would be:
./a.out -d -n Foo -i Bar
The skeleton code works fine, but I have never used getopt() and can't understand why it works correctly (understanding it has nothing to do with the assignment, I just want to make sense of it). How is it that it updates / exits the while loop? I don't see a pointer increment or the arguments passed to it in the loop change at all.
char *optString = "-d-n:-i:";
int opt = getopt(argc, argv, optString);
while (opt != -1) {
switch(opt) {
case 'd':
debug = 1;
break;
case 'n':
nameserver_flag = 1;
nameserver = optarg;
break;
case 'i':
hostname = optarg;
break;
case '?':
usage();
exit(1);
default:
usage();
exit(1);
}
opt = getopt(argc, argv, optString);
}
getopt uses global variables to store the argument index, the next character to parse and some other information. Each time you call getopt, the function checks these variables to know where it last was (or where you told it it was) and updates the variables for the next call.
Most importantly, optind stores the index in argv of the next element to be scanned.
Each call to getopt processes one more of the arguments in argv, returning the result in opt, etc. etc. What else is there to understand?

getopt value stays null

I am passing my program inputs and I could see them in argv but getopt doesnt seem to have the argument that I expect.
This is how I run my prog: ./my_prog -X -f filename
<snip>
while ((opt = getopt(argc, argv, "Xf:eE:dD")) != EOF) {
switch (opt) {
case 'X':
case 'f':
if (optarg == NULL)
fput("no point of living", fp); << for debugging
</snip>
I always get optarg as null. WHY?
Your argument string does not have a : after the X (e.g. X:f) so optarg will always be null.
I'll also point out that generally in a switch statement you'll want a break after each case (generally, not always, but when parsing arguments usually), so:
switch ( ... ) {
case 'X': {
// do something
} break;
case 'f': {
// do something else
} break;
}
For who else get to this page:
From http://www.gnu.org/software/libc/manual/html_node/Using-Getopt.html#Using-Getopt:
An option character in this string can be followed by a colon (‘:’) to indicate that it takes a required argument. If an option character is followed by two colons (‘::’), its argument is optional; this is a GNU extension.
so in your argument you might use:
"X:f:e:E:d:D:"
Had the same problem.
I just dealt with this issue, and it appears this question was never fully answered.
You have to make sure you're setting the external libc variable opterr = 0 before you call getopt; if you don't reset it and getopt previously had an error in another application anywhere in your system that used it, it will fail for the argument. I'll also reiterate the existing point that because you don't have a break statement after case 'X': that's a sure sign of an issue since it will fall through.
getopt only processes one argument at a time, so falling through case X into case f is a bad thing to do. You should always have a break in each case statement of a switch unless you are absolutely certain it should fall through (which is very rare in my experience). As another bit of general good practice, you should always enclose blocks of code in { } (referring to your conditional) unless it's a return statement or break or something that causes the program flow to to drop out of the current or parent block scope or to enter a new scope through a function or method call.
I think your option string Xf:eE:dD is fine. This indicates that:
1) The following will simply be option flags that always have a null argument: XedD
2) The following options will require an argument: fE
If this is the functionality you're looking for, the given option string is fine. If you're using GNU libc, per the other above answer, you can use :: after an option in the option string to indicate that the option might have an argument, but doesn't have to.
So at the top of your file make sure you at least have:
extern int opterr;
Then right before you call getopt for the first time in your code, set opterr to 0.
e.g.
opterr = 0;
while ((opt = getopt(argc, argv, "Xf:eE:dD")) != EOF) {
switch (opt) {
case 'X':
case 'f':
if (optarg == NULL)
fput("no point of living", fp); << for debugging
This should at least partially resolve your issue. Here's a link to an example:
http://www.gnu.org/software/libc/manual/html_node/Example-of-Getopt.html
Cheers,
Jon
I know this is old but I recently noticed changed behaviour in the way I used to use getopt years ago. Maybe it was a different environment but I find using it today requires the optarg to be DIRECTLY after the flag (no space) otherwise optarg is null.
Using your example, replace ./my_prog -X -f filename with ./my_prog -X -ffilename
I find that works fine even though it feels wrong. Hope this helps someone else out later. Make sure to try it both ways.

Resources