for my PhD I often write numerical simulations like that:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
int main(int argc, char *argv[])
{
constint method = atoi(argv[1]); // 0 for method 0, 1 for method 1 and so on
constint max_iter = atoi(argv[2]); // number of iterations in the simulation
if (method == 0) { // run method 0 for max_iter iterations }
if (method == 1) { // run method 1 for max_iter iterations }
// do other stuff
return 0;
}
To run a simulation with method 1 and 1000 iterations, I would call ./my_code.exe 1 1000
The downside is of course that the user keeps forgetting about the role of the different arguments and its order.
I would like to call something like ./my_code.exe -method methodname -max_iter some_integer
and it would be great if one could also switch the order of arguments.
I am not at all familiar with the parsers out there. Some of them seem to be a bit over-my-head and maybe too complex for this setting.
Is there a good recommendation for what to use? Or do you have any tips on how to write my own parser?
typically you code it so that if there are NO command line inputs and/or the user types "-?" or some similar 'help me!' code, you dump a 'how to use this program' text output and exit the program gracefully. Try to not be obtuse about it; many older or unix origin command line programs give you crap like usage <x>{-tgif}:hoopajoo. Just tell them what to type in simple, easy to understand words with an example and keep it simple (the more options and special symbols and such you inject, the harder it is to use and remember).
you can of course have default values and run those if the command line input is wrong, if that is useful.
while you can use a library and all, if you just need 2 or 3 simple arguments that are not optional you don't need all that: the above reminder is all you need. If you want to get into optional flags and actual parsing (instead of just fixed order, not optional inputs) then you should go with the above.
#include <iostream>
#include <cstdlib>
struct Args {
int method {}; // Default value for method
int iter { 1 }; // Default value for max-iter
};
void usage(constchar* prg) {
std::cout << "Usage: " << prg << " {-method <num>} { -iter_max <num>}\n";
}
// Return true OK, false bad
bool parse(int argc, char* argv[], Args& args) {
bool gotmethod {};
bool gotiter {};
bool good {true};
Args ags {};
for (int a { 1 }; a < argc; a += 2)
if (strcmp(argv[a], "-method") == 0) {
if (gotmethod) {
std::cout << "Duplicate -method option\n";
good = false;
} else {
char* endptr;
gotmethod = true;
ags.method = std::strtol(argv[a + 1], &endptr, 10);
if (*endptr != 0) {
std::cout << "Invalid method number specified\n";
good = false;
}
}
} elseif (strcmp(argv[a], "-max_iter") == 0) {
if (gotiter) {
std::cout << "Duplicate -max_iter option\n";
good = false;
} else {
char* endptr;
gotiter = true;
ags.iter = std::strtol(argv[a + 1], &endptr, 10);
if (*endptr != 0) {
std::cout << "Invalid max_iter number specified\n";
good = false;
}
}
} else {
std::cout << "Unknown option '" << argv[a] << "' specified\n";
good = false;
}
if (good)
args = ags;
return good;
}
int main(int argc, char* argv[]) {
Args args;
if (parse(argc, argv, args)) {
std::cout << "Method is: " << args.method << '\n';
std::cout << "Max_iter is: " << args.iter << '\n';
} else
usage(argv[0]);
}
Note that this simple method doesn't scale very well and even for 3 args gets unwieldy. In those cases a more complex but general parser is probably required.
When the order of operation of the command line is integral to the program, then I tend to use a for loop inside of main. This allows each argument full access to the whole program, and you can run the commands in a sequential order as provided by the user. Some of the arguments can also be run out of order if required (see line 35: -f filePath).
For brevity I do skip checking the syntax of the input number, I really should have created a bool isDouble(std::string val) function.
Here's an example using getopt().
- It prints a usage message if there are no options, or if they are wrong, or if the method
or count is out of bounds. This includes when the user types mycode -? or mycode -h, which are the most common "give me help" options.
- The usage message also says what the program does (which I completely made up). This is helpful for when you can't remember and run it again in 4 years.
#include <iostream> //Required for cin, cout
#include <unistd.h> // for getopt()
using std::cout;
using std::string;
void usage(constchar *argv0)
{
cout << "This program simulates doughnut frosting erors when\n";
cout << "making \"count\" doughnuts with \"method\" frosting method\n\n";
cout << "Usage: " << argv0 << " -m method -c count\n";
}
int
main(int argc, char **argv)
{
int opt; // note that opt is an int, not a char
int count = -1, method = -1; // You could change these to some default
// values if it made sense
// Note the "=" below is intentional. It shouldn't be ==
while ((opt = getopt(argc, argv, "m:c:")) != EOF) {
switch (opt) {
case'm':
method = atoi(optarg); // optarg is the thing that follows -m
break;
case'c':
count = atoi(optarg);
break;
default:
usage(argv[0]);
return 1;
}
}
if (optind < argc || method<0 || count<0) {
// There's more stuff on the command line, like "my_code.exe foobar",
// or they didn't specify both -m and -c options, or one of them
// was invalid like -m freddy.
usage(argv[0]);
return 1;
}
cout << "Running simulation with method " << method
<< " and count " << count << '\n';
return 0;
}
$ ./foo
This program simulates doughnut frosting erors when
making "count" doughnuts with "method" frosting method
Usage: ./foo -m method -c count
dhayden@DHAYDENHTZK3M2 ~/tmp
$ ./foo -m 123
This program simulates doughnut frosting erors when
making "count" doughnuts with "method" frosting method
Usage: ./foo -m method -c count
dhayden@DHAYDENHTZK3M2 ~/tmp
$ ./foo -m 18 -c 333
Running simulation with method 18 and count 333