How to parse command line parameters.
Command-line parameters are passed to a program at runt-time by the operating system when the program is requested by another program, such as a command interpreter («shell») like cmd.exe on Windows or bash on Linux and OS X. The user types a command and the shell calls the operating system to run the program. Exactly how this is done is beyond the scope of this article (on Windows, look up CreateProcess; on UNIX and UNIX-like systems look up fork(3) and exec(3) in the manual).
- Modifying program behaviour — command-line parameters can be used to tell a program how you expect it to behave; for example, some programs have a -q (quiet) option to tell them not to output as much text.
- Having a program run without user interaction — this is especially useful for programs that are called from scripts or other programs.
The command-line
Adding the ability to parse command-line parameters to a program is very easy. Every C and C++ program has a main function. In a program without the capability to parse its command-line, main is usually defined like this:
To see the command-line we must add two parameters to main which are, by convention, named argc (argument count) and argv (argument vector [here, vector refers to an array, not a C++ or Euclidean vector]). argc has the type int and argv usually has the type char** or char* [] (see below). main now looks like this:
int main(int argc, char* argv[]) // or char** argv
argc tells you how many command-line arguments there were. It is always at least 1, because the first string in argv (argv[0]) is the command used to invoke the program. argv contains the actual command-line arguments as an array of strings, the first of which (as we have already discovered) is the program’s name. Try this example:
int main(int argc, char* argv[]) < std::cout return 0; >
This program will print the name of the command you used to run it: if you called the executable «a.exe» (Windows) or «a.out» (UNIX) it would likely print «a.exe» or «./a.out» (if you ran it from the shell) respectively.
Earlier it was mentioned that argc contains the number of arguments passed to the program. This is useful as it can tell us when the user hasn’t passed the correct number of arguments, and we can then inform the user of how to run our program:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int main(int argc, char* argv[]) < // Check the number of parameters if (argc < 2) < // Tell the user how to run the program std::cerr "Usage: " << argv[0] " NAME" /* "Usage messages" are a conventional way of telling the user * how to run a program if they enter the command incorrectly. */ return 1; > // Print the user's name: std::cout << argv[0] "says hello, " << argv[1] "!" return 0; >
Arguments and Parameters
Arguments and parameters are strings passed to your program to give it information. A program for moving files, for example, may be invoked with two arguments — the source file and the destination: move /path/to/source /path/to/destination (note: on Windows these paths would use backslashes instead [and would probably have a drive prefix, like C:], however as Windows supports backwards and forwards slashes in paths while UNIX systems only support forward slashes, forward slashes will be used throughout this article).
In this example, the program would look something like this:
int main(int argc, char* argv[]) < if (argc < 3) < // We expect 3 arguments: the program name, the source path and the destination path std::cerr "Usage: " << argv[0] "SOURCE DESTINATION" return 1; > return move(argv[1], argv[2]); // Implementation of the move function is platform dependent // and beyond the scope of this article, so it is left out. >
If we wanted to allow the use of multiple source paths we could use a loop and a std::vector:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int main(int argc, char* argv[]) < if (argc < 3) < // We expect 3 arguments: the program name, the source path and the destination path std::cerr "Usage: " << argv[0] "SOURCE DESTINATION" return 1; > std::vector sources; std::string destination; for (int i = 1; i < argc; ++i) < // Remember argv[0] is the path to the program, we want from argv[1] onwards if (i + 1 < argc) sources.push_back(argv[i]); // Add all but the last argument to the vector. else destination = argv[i]; > return move(sources, destination);
Arguments may be passed as values to options. An option usually starts with a single hyphen (-) for a «short option» or a double hyphen (—) for a «long option» on UNIX, or a forward slash on Windows. Hyphens (single and double) will be used in this article. Continuing the example of the move program, the program could use a -d/--destination option to tell it which path is the source and which is the destination, as in move -d /path/to/destination /path/to/source and move --destination /path/to/destination /path/to/source. Options are always right-associative, meaning that the argument to an option is always the text directly to the right of it.
Let’s extend the previous example to use the destination option.
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
int main(int argc, char* argv[]) < if (argc < 3) < std::cerr "Usage: " << argv[0] "--destination DESTINATION SOURCE" return 1; > std::vector sources; std::string destination; for (int i = 1; i < argc; ++i) < if (std::string(argv[i]) == "--destination") < if (i + 1 < argc) < // Make sure we aren't at the end of argv! destination = argv[i++]; // Increment 'i' so we don't get the argument as the next argv[i]. > else < // Uh-oh, there was no argument to the destination option. std::cerr "--destination option requires one argument." return 1; > > else < sources.push_back(argv[i]); >> return move(sources, destination); >
Now the parameters can be in any order as long as the destination path is immediately to the right of «—destination».
More on usage messages
Our usage message is helpful, but if we need to print it from more than one place, we have to copy the code. Obviously the way around this is to use a 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
static void show_usage(std::string name) < std::cerr "Usage: " << argv[0] " SOURCES" "Options:\n" "\t-h,--help\t\tShow this help message\n" "\t-d,--destination DESTINATION\tSpecify the destination path" int main(int argc, char* argv[]) < if (argc < 3) < show_usage(argv[0]); return 1; > std::vector sources; std::string destination; for (int i = 1; i < argc; ++i) < std::string arg = argv[i]; if ((arg == "-h") || (arg == "--help")) < show_usage(argv[0]); return 0; > else if ((arg == "-d") || (arg == "--destination")) < if (i + 1 < argc) < // Make sure we aren't at the end of argv! destination = argv[i++]; // Increment 'i' so we don't get the argument as the next argv[i]. > else < // Uh-oh, there was no argument to the destination option. std::cerr "--destination option requires one argument." return 1; > > else < sources.push_back(argv[i]); >> return move(sources, destination); >
Now, rather than having to guess, the user can call our program with the -h or --help options to find out how to run the command.
Getopt
These methods of finding command-line arguments are simple and not very robust. The best way of finding options is using the getopt family of functions:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int getopt(int argc, char * const argv[], const char *optstring); extern char *optarg; extern int optind, opterr, optopt; int getopt_long(int argc, char * const argv[], const char *optstring, const struct option *longopts, int *longindex); int getopt_long_only(int argc, char * const argv[], const char *optstring, const struct option *longopts, int *longindex);
The manual page has examples of how to use them.