How to get the arguments from getopt to work together - c

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);

Related

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;
}

getopt with repeated and optional arguments

For a simple C project of a filesystem in a file, I have to make a command for writing the partitions table. It just contains the number of partitions and the size of them, pretty simple.
It should work like mk_part -s size [-s size ...] [name].
[name] is the filename of the disk, it's optionnal because there is a default one provided.
I don't know much getopt_long (and getopt) but all I read is that I while get options in a while so the two way of processing for me would be :
store all the sizes in an array and then write them in the table.
write size directly during parsing
For the first choice the difficulty is that I don't know the number of partitions. But I still could majorate this number by argc or better by (argc-1)/2 and it would work.
For the second choice I don't know which file to write.
So what is the best alternative to get all those arguments and how can I get this optionnal name ?
getopt can handle both repeated and optional args just fine. For repeated args each invocation of getopt will give you the next arg. getopt doesn't care that it is repeated. For the arg at the end, just need to check for its presence once all the options are parsed. Below is code modified from the example in the getopt man page to handle your scenario:
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
int
main(int argc, char *argv[])
{
int opt;
while ((opt = getopt(argc, argv, "s:")) != -1) {
switch (opt) {
case 's':
printf("size=%d\n", atoi(optarg));
break;
default: /* '?' */
exit(EXIT_FAILURE);
}
}
if (optind < argc) {
printf("name=%s\n", argv[optind]);
} else {
printf("optional name arg not present\n");
}
exit(EXIT_SUCCESS);
}
And here is some sample runs of the program showing it handling repeated options and the arg at the end.
$ ./a.out -s 10 -s 20 -s 30
size=10
size=20
size=30
optional name arg not present
$ ./a.out -s 1 my_name
size=1
name=my_name
I think you're overthinking this. I know it's always tempting to try to avoid malloc's, but the efficiency of option parsing is never (*) important. You only parse options once, and in the cost of initializing a new process, finding the executable, linking and loading it, and all the rest of the process of starting up a command, the time it takes to parse options is probably not even noise.
So just do it in the simplest way possible. Here's one possible outline:
int main(int argc, char* argv) {
/* These variables describe the options */
int nparts = 0; // Number of partitions
unsigned long* parts = NULL; // Array of partitions (of size nparts)
const char* diskname="/the/default/name"; // Disk's filename
for (;;) {
switch (getopt(argc, argv, "s:")) {
case '?':
/* Print usage message */
exit(1);
case 's':
/* Some error checking missing */
parts = realloc(parts, ++nparts * sizeof *parts);
parts[nparts - 1] = strtoul(optarg, NULL, 0);
continue;
case -1:
break;
}
break;
}
if (optind < argc) diskname = argv[optind++];
if (optind != argc) {
/* print error message */
exit(1);
}
return do_partitions(diskname, parts, nparts);
}
The above code is missing a lot of error checking and other niceties, but it's short and to the point. It simply reallocs the partition array every time a new size is found. (That's probably not as awful as you think it is, because realloc itself is probably clever enough to increase the allocation size exponentially. But even if it were awful, it's not going to happen often enough to even notice.)
The trick with continue and break is a common way of nesting a switch inside a for. In the switch, continue will continue the for loop, while break will break out of the switch; since all the switch actions which do not terminate the for loop continue, whatever follows the switch block is only executed for a switch action which explicitly breaks. So the break following the switch block breaks the for loop in precisely those cases where the switch action did a break.
You might want to check that there was at least one partition size defined before call the function which does the repartitioning.

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_long() to parse more than one argument to a single option letter

I want to use getopt_long() to parse command-line arguments. After reading the getopt_long() man pages, I have the understanding that getopt_long() only can parse one argument after an option. Is there anyway to use getopt_long() to parse this command line like this:
./a.out -s 127.0.0.1 2012 -u stackoverflow
To give the result:
ip = 127.0.0.1
port = 2012
username = stackoverflow
Here's what I've tried:
while (1) {
int this_option_optind = optind ? optind : 1;
int option_index = 0;
static struct option long_options[] = {
{"server", required_argument, NULL, 's'},
{"user", required_argument, NULL, 'u'},
{0, 0, 0, 0},
};
c = getopt_long(argc, argv, "s:u:", long_options, &option_index);
if (c == -1)
break;
switch (c) {
case 's':
printf("option %s", long_options[option_index].name);
if (optarg) {
printf(" with arg %s", optarg);
}
printf("\n");
case 'u':
printf("option %s", long_options[option_index].name);
if (optarg) {
printf(" with arg %s", optarg);
}
printf("\n");
case '?':
break;
default:
printf("?? getopt returned character code 0%o ??\n", c);
}
}
The first answer is: nothing stops you from using or modifying optind. If optind < argc then optind is the index of the next common-line argument in argv, and otherwise there are no more arguments. So you can use that argument in your processing loop, but it's your responsibility:
to ensure that optind is in range (< argc)
to check whether the argument at argv[optind] is another option or not (i.e. whether it starts with a -)
to increment optind so that the argument you've used doesn't get rescanned by getopt.
The second answer is: you should think three times before doing something non-standard like this. Although it might seem like a bit more typing, there are good reasons to use a more standard technique, like a -p PORT option. It's easier to document, less work to implement, and less surprising for users accustomed to standard command-line option behaviour.
Finally, you're missing a number of break statements in your example code, which is why -s will be reported as also being -u.
If you'd call your program like this:
./a.out -s "127.0.0.1 2012" -u stackoverflow
you'd be getting "127.0.0.1 2012" as value for optarg in the 's' case.

Resources