I understand how to use getopt to accept command-line arguments like
./program -a yes -b no
What I am currently trying to do is accept command-line arguments where some are optional and some are not.
For example:
./program argv[1] argv[2] -a yes -b no
Options after multiple optional arguments is probably bad idea; don't design the command line syntax that way, if you can help it.
That said, you can parse the arguments yourself outside of getopt until you see something which looks like an option (while incrementing argv and decrementing argc). Then use getopt for the remainder of the command line from that point on.
Pseudo-code:
for (; *argv; argc--, argv++) {
if (argv looks like an option)
break;
process *argv somehow
}
now process with getopt(argc, argv, ...)
Related
I am trying to use execvp() to run less however I keep running into the same error saying that:
Missing filename ("less --help" for help)
I'm assuming I am trying to input the file completely wrong. Could anyone give me some guidance? Here is my code line trying to implement it:
// args[0] == "tempFile" which is in my directory
execvp("less", args)
argv[0] is meant to be the name you give to the command. The command you execute uses that to know how it was invoked. Typically, you'd want to use something like less here:
argv[0] = "less";
argv[1] = "filename";
argv[2] = NULL;
execvp("less", argv);
Argument 0, taken from argv[0], is conventionally the command name. Typically it's the same string that you pass as the first argument to execvp (that's what shells do).
Argument 1, from argv[1], is the first “real” argument. So pass a 3-element array containing: char *args[] = {"less", "tempFile", NULL}
Most languages follow the same argument numbering. For example, if you invoke a shell script, it sees what you pass as argv[0] as its $0, what you pass as argv[1] as $1, etc. Perl is a notable exception: in a Perl script, argv[0] is $0, argv[1] is $ARGV[0], argv[2] is $ARGV[1], etc.
Just as when you execute a shell script on the command line with
sh -c 'script body here' arg0 arg1 arg2
the arg0 argument gets placed in $0 (this is usually the name of the process) and is not really counted as one of the command line arguments of the script itself (it's not part of $# and $# will not count it). The first command line argument available in $1 is arg1.
In your case, use args[0] = "less" and args[1] = "tempFile" in your C code. args[3] should be a null pointer.
I am trying to write a limited version of ls w/ some options.
However, I am stuck on the problem of parsing out my options from my arguments in a clean manner.
For example:
$ ls -l -t somefile anotherFile
$ ls somefile -lt anotherFile
have the same behavior.
This poses two problems for me:
It makes using argc a bit more difficult. For example I would consider the arguments ls -lt and ls to both have 0 arguments (other than the name of the command) however argc counts -l as an argument.
Therefore the naive implementation of :
if( argc == 1) {list all the contents of cwd}
does not work.
Is there a built-in way to get the options as well as the option count, or do I have to roll my own function?
I have to consider all the different ways options can be arranged and be careful not to get an option mixed up as a file name or directory name. It seems like the cleanest solution is to separate the options from the file arguments from the start. Is there an idiomatic way to do this / is there standard library calls that do this?
There is no built-in argument parsing help, but getopt is the "standard" method for argument parsing.
For simple apps, I sometimes roll my own with something like:
int pos=0;
argc--;argv++;
while (argc > 0) {
if (*argv[0]=='-') {
switch ((*argv)[1]) {
case 'l': //-l argument
save_option_l(++argv);
argc--; //we consumed one name
break;
//... other -options here ...
default:
usage("unrecognized option %s", *argv);
}
}
else {
save_positional_argument(argv,pos++);
}
argv++;
argc--;
}
In this case, I require the modifiers to directly follow the flags. Don't support variable usage like your first example, unless there are very strong reasons to do so.
If you have Gnu's implementation of getopt, it will do all that for you.
Posix standard getopt terminates option processing when it hits the first non-option argument. That conforms to Posix guidelines for utility argument parsing, and many of us prefer this behaviour. But others like the ability to intermingle options and non-options, and that's the norm for Gnu utilities unless you set an environment variable with the ungainly name POSIXLY_CORRECT.
Consistent with that preference, Gnu getopt parses arguments:
The default is to permute the contents of argv while scanning it so that eventually all the non-options are at the end. This allows options to be given in any order, even with programs that were not written to expect this.
Note the wording about permuting arguments. This means that if you start with
ls somefile -lt anotherFile
Gnu getopt will:
Report a l
Report a t
Report end of options (-1), leaving optind with the value 2 and argv now looking like:
ls -lt somefile anotherFile
So now you can process your non-option arguments with:
for (int argno = optind; argno < argc; ++argno) {
/* Do something with argv[argno] */
}
Also, you can tell how many non-option arguments you received with argc-optind, and if argc == optind, you know there weren't any.
Unbundling -lt into two options is standard Posix getopt behaviour. You can combine options lime that as long as the first one doesn't take an argument.
I am trying to parse command line arguments in C. Currently, I am using getopt do the parse. I have something like this:
#include <unistd.h>
int main(int argc, char ** argv)
{
while((c=getopt(argc, argv, "abf:")) != -1)
{
switch(c)
{
case 'a':
break;
case 'b':
break;
case 'f':
puts(optarg);
break;
case ':':
puts("oops");
break;
case '?'
puts("wrong command");
break;
}
}
}
then need to use ./a.out -fto run the program, and -f is the command element, but looks like -f must start with a '-', if I do not want the command element starts with '-', i.e, using ./a.out f instead of ./a.out -f, how to achieve it?
if getopt does not support parsing a command line in this way, are there any other library to use in C?
The argc and argv variables give you access to what you're looking for. argc is "argument count" and argv is "argument vector" (array of strings).
getopt is a very useful and powerful tool, but if you must not start with a dash, you can just access the argument array directly:
int main( int argc, char** argv) {
if( argc != 1) { /* problem! */ }
char * argument = argv[1]; // a.out f ... argv[1] will be "f"
}
You could use (on Linux with GNU libc) for parsing program arguments:
getopt with getopt_long; you might skip some arguments using tricks around optind
argp which is quite powerful
and of course you could parse program arguments manually, since they are given thru main(int argc, char**argv) on Linux (with the guarantee that argc>0, that argv[0] is "the program name" -e.g. to find it in your $PATH when it contains no / ..., that argv[argc] is the NULL pointer, and that before that every argv[i] with i<argc and i>0 is a zero-terminated string. See execve(2) for more.
GNU coding standards: command line interfaces document quite clearly some conventions. Please, obey at least the --help and --version conventions!
You might also be concerned by customizing the shell auto-completion facilities. GNU bash has programmable completion. zsh has a sophisticated completion system.
Remember that on Posix and Linux the globbing of command words is done by the shell before starting your program. See glob(7).
The getopt library will stop parsing at the first non-option argument. For command-based programs, this will be at the command name. You can then set optind to the index to start at and run getopt again with the command-specific arguments.
For example:
// general getopts
if (optind >= argc) return 0; // error -- no command
if (strcmp(argv[optind], "command") == 0)
{
++optind; // move over the command name
// 'command'-specific getopts
if (optind >= argc) return 0; // error -- no input
}
This should allow git-like command-line parsing.
Let's say I made a C program that is called like this:
./something -d dopt filename
So -d is a command, dopt is an optional argument to -d and filename is an argument to ./something, because I can also call ./something filename.
What is the getopt form to represent get the filename?
Use optstring "d:"
Capture -d dopt with optarg in the usual way. Then look at optind (compare it with argc), which tells you whether there are any non-option arguments left. If so, your filename is the first of these.
getopt doesn't specifically tell you what the non-option arguments are or check the number. It just tells you where they start (having first moved them to the end of the argument array, if you're in GNU's non-strict-POSIX mode)
Check-out how grep does it. At the end of main() you'll find:
if (optind < argc)
{
do
{
char *file = argv[optind];
// do something with file
}
while ( ++optind < argc);
}
The optind is the number of command-line options found by getopt. So this conditional/loop construct can handle all of the files listed by the user.
My argument is like this
./a.out -i file1 file2 file3
How can I utilize getopt() to get 3 (or more) input files?
I'm doing something like this:
while ((opt = getopt(argc, argv, "i:xyz.."))!= -1){
case 'i':
input = optarg;
break;
...
}
I get just the file1; how to get file2, file3?
I know this is quite old but I came across this in my search for a solution.
while((command = getopt(argc, argv, "a:")) != -1){
switch(command){
case 'a':
(...)
optind--;
for( ;optind < argc && *argv[optind] != '-'; optind++){
DoSomething( argv[optind] );
}
break;
}
I found that int optind (extern used by getopt() ) points to next position after the 'current argv' selected by getopt();
That's why I decrease it at the beginning.
First of all for loop checks if the value of current argument is within boundaries of argv (argc is the length of array so last position in array argv is argc-1).
Second part of && compares if the next argument's first char is '-'. If the first char is '-' then we run out of next values for current argument else argv[optind] is our next value. And so on until the argv is over or argument runs out of values.
At the end increment optind to check for the next argv.
Note that because we are checking 'optind < argc' first second part of condition will not be executed unless first part is true so no worries of reading outside of array boundaries.
PS I am a quite new C programmer if someone has an improvements or critique please share it.
If you must, you could start at argv[optind] and increment optind yourself. However, I would recommend against this since I consider that syntax to be poor form. (How would you know when you've reached the end of the list? What if someone has a file named with a - as the first character?)
I think that it would be better yet to change your syntax to either:
/a.out -i file1 -i file2 -i file3
Or to treat the list of files as positional parameters:
/a.out file1 file2 file3
Note that glibc's nonconformant argument permutation extension will break any attempt to use multiple arguments to -i in this manner. And on non-GNU systems, the "second argument to -i" will be interpreted as the first non-option argument, halting any further option parsing. With these issues in mind, I would drop getopt and write your own command line parser if you want to use this syntax, since it's not a syntax supported by getopt.
I looked and tried the code above, but I found my solution a little easier and worked better for me:
The handling I wanted was:
-m mux_i2c_group mux_i2c_out
(2 arguments required).
Here's how it panned out for me:
case 'm':
mux_i2c_group = strtol(optarg, &ch_p, 0);
if (optind < argc && *argv[optind] != '-'){
mux_i2c_out = strtol(argv[optind], NULL, 0);
optind++;
} else {
fprintf(stderr, "\n-m option require TWO arguments <mux_group> "
"<mux_out>\n\n");
usage();
}
use_mux_flag = 1;
break;
This grabbed the first value form me as normal and then just looked for the second, REQUIRED value.
The solution by GoTTimw has proven very useful to me. However, I would like to mention one more idea, that has not been suggested here yet.
Pass arguments as one string in this way.
./a.out -i "file1 file2 file3"
Then you get one string as a single argument and you only need to split it by space.