Command line parser for scientific computing

Greetings,

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[])
{

   const int method = atoi(argv[1]);      // 0 for method 0, 1 for method 1 and so on
   const int 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?

Best,
PiF
Last edited on
Writing your own parser isn't rocket science, but usually I'd recommend to simply go with existing libraries/standards 😏

getopt() is kind of a de facto standard for command-line parsing:
https://en.wikipedia.org/wiki/Getopt

See also:
https://www.gnu.org/software/libc/manual/html_node/Example-of-Getopt.html
https://www.gnu.org/software/libc/manual/html_node/Getopt-Long-Option-Example.html

Since Windows doesn't come with get getopt() by default, there are ports:
https://github.com/Chunde/getopt-for-windows
Last edited on
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.
Thank you for the comments, guys!

I'll see what I do.
Definitely just do it by hand if its simple enough.
For the very simplest cases you can just print a usage message:
1
2
3
4
5
6
7
8
9
10
#include <iostream>

void usage(char const* argv0) 
{ std::cerr << "usage: " << argv0 << " input_file output_file [iteration_count]\n"; }

int main(int argc, char** argv)
{
  if (argc < 3 || argc > 4) { usage(argv[0]); return 1; }
  // ... 
}
Last edited on
Have you looked at that Chunde getopt code? It still uses K&R function param style! Even as c code - yuk.

For C++ I use this:
https://github.com/iorate/getoptmm

which is for C++14 (and I think is now part of Boost). There's probably even better versions of getopt() now available.

For specific cases it's also fairly easy to code your own.
Last edited on
@mbozzi - Check L6 with L1 ???
For this case of arg parsing simply, consider:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
#include <iostream>
#include <cstdlib>

struct Args {
	int method {};		// Default value for method
	int iter { 1 };		// Default value for max-iter
};

void usage(const char* 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;
				}
			}
		} else if (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.
Last edited on
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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
#include <iostream>
#include <string>
#include <fstream>

void help(std::string progName)
{
        std::cout << progName << " can be used as a tiny calculator, here's an example" << std::endl;
        std::cout << progName << " -v 3 -a 2 -t 4" << std::endl;
        std::cout << "which sets the current value to 3, adds 2, then multiplies the result by 4." << std::endl;

}

int main(int argc, char ** argv)
{
        double value = 0;
        std::string filePath = "";
        for(int i = 1; i < argc; i ++)
        {
                std::string arg = argv[i];
                if(arg == "-v" && i + 1 < argc)
                {
                        value = std::stod(argv[i + 1]);
                        i ++;
                }
                else if(arg == "-a" && i + 1 < argc)
                {
                        value += std::stod(argv[i + 1]);
                        i ++;
                }
                else if(arg == "-t" && i + 1 < argc)
                {
                        value *= std::stod(argv[i + 1]);
                        i ++;
                }
                else if(arg == "-f" && i + 1 < argc)
                {
                        filePath = argv[i + 1];
                        i ++;
                }
                else if(arg == "-h")
                {
                        help(argv[0]);
                }
                else
                {
                        std::cout << "Hmm, " << argv[i] << " is not viable\n";
                        help(argv[0]);
                        return 0;
                }
        }
        if(filePath.empty())
        {
                std::cout << value << std::endl;
        }
        else
        {
                std::cout << "Save output to " << filePath << std::endl;
        }

}

Last edited on
@mbozzi - Check L6 with L1 ???

Thanks!
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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include <iostream>				 //Required for cin, cout
#include <unistd.h>				 // for getopt()

using std::cout;
using std::string;

void usage(const char *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

Topic archived. No new replies allowed.