Home » Linux/Unix » Boost Command Line Argument Processing

Boost Command Line Argument Processing

Start here

Several months ago I was approached by some co-works who thought it would be useful to develop a platform for writing scientific applications. As we talked, a general plan took shape; we would take our best solutions and refactor them as generalized solutions to common problems we encountered.

As a starting point, we decided that we would address the problem of command line argument processing. We knew that several solutions already existed. For instance we could use getopt() but a C based solution seemed so archaic and inflexible. Finally, we decided that we would dust off an in-house developed command line processor written by one of our researchers and make it suitable for a more general audience.

As I began the process of generalizing the component, I wondered if there was something in the Boost libraries that could address the problem. It did not take long before I stumbled across the Boost program options library. The Boost program options library is a general purpose command line processor with an impressive set of capabilities. The discussion and example below should give you a general idea of how to use the Boost program options library in your own applications.


How to use Boost for Command Line Argument Processing

Here the general workflow you will want to follow when using the Boost program options library:

  • Include the headers relevant to the boost::program_options library. I have included a list in the example source code below for your reference. Do not forget to include std::exception so you can handle any argument errors emitted by the command line parser. It is good practice to print the exception followed by the standard help text so the user can understand what went wrong.
  • Create an options description object and add descriptive text for display when help argument is supplied by the user.
  • Add the definition of each command line argument to your options description object using one of two formats:

(“long-name,short-name”, “Description of argument”) for flag values or

(“long-name,short-name”, <data-type>, “Description of argument”) for arguments with values

  • Remember that arguments with values may have multiple values (i.e. there may be multiple occurrences of an argument specification on a single command line), therefore all valued arguments must be vectors of the base argument type.
  • If your command has position arguments (i.e. arguments without a preceding -tag) map them to their tag valued counterparts using a positional options description object. In the example below I will do this with the –input-file parameter.
  • Create a container (variables map) to be used by the command line parser to store any parsed arguments and their values.
  • Bind the options description and positional options description objects to the command line parser and run the parser. Do not forget to catch and display any exceptions emitted by the parser.
  • As another good practice, add a help argument handler to display help text when requested by the user.
  • Finally, your program is ready to consume the processed command line arguments. When accessing these arguments use one of these two patterns:

The presence of command line flags can be detected by sampling the value count of the specific argument found in the variable map. Here is an example:

if (variable_map.count(“restart”))
{
    cout << “–restart specified” << endl;
}

Valued arguments may be retrieved by using a casting method provided by the variable map. Note that the casting method is template function and must be supplied with the same vectored value type supplied to the options description object.

if (variable_map.count(“output-file”))
{
    vector<string> outputFilename =
        vm[“output-file”].as< vector<string> >();
    cout << “–output-file specified with value = ”
        
<< outputFilename[0] << endl;
}


A Concrete Example

Assume we want to write an application with the following command line arguments:

Command [-h|–help] [-m|–memory-report] [-r|–restart] [-t|–template] [-v|–validate] [-o|-output-file <file name>] -i|-input-file <file name>



Also assume, for legacy reasons, we want to support the ability to specify the input file positionally as well, like this:

Command [-h|–help] [-m|–memory-report] [-r|–restart] [-t|–template] [-v|–validate] [-o|-output-file <file name>] <file name>


From the above we know that the -h, -m, -r, -t and -v are command line flags. We can also see that both the -o and -i arguments take a file name as an argument value. Except for input file handling our application command line is fairly straightforward. The source code below illustrates how we would use the boost_program_options library to implement our application command line parsing functionality:

[CommandLine.cpp]
// Include the headers relevant to the boost::program_options
// library
#include <boost/program_options/options_description.hpp>
#include <boost/program_options/parsers.hpp>
#include <boost/program_options/variables_map.hpp>
#include <boost/tokenizer.hpp>
#include <boost/token_functions.hpp>

using namespace boost;
using namespace boost::program_options;

#include <iostream>
#include <fstream>

// Include std::exception so we can handling any argument errors
// emitted by the command line parser
#include <exception>

using namespace std;

int main(int argc , char **argv)
{

// Add descriptive text for display when help argument is
// supplied
options_description desc(
    “\nAn example command using Boost command line “
    ”arguments.\n\nAllowed arguments”);

// Define command line arguments using either formats:
//
//     (“long-name,short-name”, “Description of argument”)
//     for flag values or
//
//     (“long-name,short-name”, <data-type>, 
//     “Description of argument”) arguments with values
//
// Remember that arguments with values may be multi-values
// and must be vectors
desc.add_options()
    (“help,h”, “Produce this help message.”)
    (“memory-report,m”, “Print a memory usage report to “
     ”the log at termination.”)
    (“restart,r”, “Restart the application.”)
    (“template,t”, “Creates an input file template of “
     ”the specified name and then exits.”)
    (“validate,v”, “Validate an input file for correctness ”
     ”and then exits.”)
    (“output-file,o”, value< vector<string> >(),
     “Specifies output file.”)
    (“input-file,i”, value< vector<string> >(),
     “Specifies input file.”);

// Map positional parameters to their tag valued types 
// (e.g. –input-file parameters)
positional_options_description p;
p.add(“input-file”, -1);

// Parse the command line catching and displaying any 
// parser errors
variables_map vm;
try
{
    store(command_line_parser(
    argc, argv).options(desc).positional(p).run(), vm);
    notify(vm);
} catch (std::exception &e)
{
    cout << endl << e.what() << endl;
    cout << desc << endl;
}

// Display help text when requested
if (vm.count(“help”))
{
    cout << “–help specified” << endl;
    cout << desc << endl;
}

// Display the state of the arguments supplied
if (vm.count(“memory-report”))
{
    cout << “–memory-report specified” << endl;
}

if (vm.count(“restart”))
{
    cout << “–restart specified” << endl;
}

if (vm.count(“template”))
{
    cout << “–template specified” << endl;
}

if (vm.count(“validate”))
{
    cout << “–validate specified” << endl;
}

if (vm.count(“output-file”))
{
    vector<string> outputFilename =
        vm[“output-file”].as< vector<string> >();
    cout << “–output-file specified with value = “
        << outputFilename[0] << endl;
}

if (vm.count(“input-file”))
{
    vector<string> inputFilename =
        vm[“input-file”].as< vector<string> >();
    cout << “–input-file specified with value = “
        << inputFilename[0] << endl;
}

return EXIT_SUCCESS;

}


Compiling and linking your application using the Boost program options library is relatively straight forward. During compilation, remember to add the Boost include directory using the -I compiler option. When linking, specify the location of the Boost libraries using the -L option and include the program options library using the -l option. Here is a copy of the Makefile I used to compile the example code above:

[Makefile]
CXX = clang++
CXXFLAGS = -O2 -g -Wall -std=c++11 -fmessage-length=0
INCLUDES := -I ~/boost
LD := clang++
LDFLAGS := -L ~/boost/lib -lboost_program_options
SOURCES := $(shell find . -depth 1 -name ‘*.cpp’ -print | sort)
OBJECTS := $(SOURCES:.cpp=.o)
TARGETS = CommandLine

%.o:%.cpp
    $(CXX) $(CXXFLAGS) $(INCLUDES) -c $< -o $@

all: $(TARGETS)

CommandLine: CommandLine.o
    $(LD) $(LDFLAGS) -o $@ $<
    chmod 755 $@

clean:
    rm -f $(TARGETS) $(OBJECTS)



Now that we have a running application, we will try invoking it with several different combinations of arguments. First use the help argument. Here is a listing of the results:

Examples$ ./CommandLine -h
–help specified
An example command using Boost command line arguments.
Allowed arguments:
-h [ –help ] Produce this help message.
-m [ –memory-report ] Print a memory usage report to the log at termination.
-r [ –restart ] Restart the application.
-t [ –template ] Creates an input file template of the specified name and then exits.
-v [ –validate ] Validate an input file for correctness and then exits.
-o [ –output-file ] arg Specifies output file.
-i [ –input-file ] arg Specifies input file.



Next we can check to see if our other arguments are processed correctly:

Examples$ ./CommandLine -m -r -t -v -o outfile -i infile
–memory-report specified
–restart specified
–template specified
–validate specified
–output-file specified with value = outfile
–input-file specified with value = infile



Since the -i argument is both -tag and positional, wee will check to see if our application can recognize it as a positional argument:

Examples$ ./CommandLine –memory-report –restart –template –validate –output-file outfile infile
–memory-report specified
–restart specified
–template specified
–validate specified
–output-file specified with value = outfile
–input-file specified with value = infile



Finally, we can try an example command invocation with an invalid argument (e.g. -bad-arg):

Examples$ ./CommandLine –memory-report –restart –template –validate –output-file outfile infile -bad-arg
unrecognised option ‘-bad-arg’
An example command using Boost command line arguments.
Allowed arguments:
-h [ –help ] Produce this help message.
-m [ –memory-report ] Print a memory usage report to the log at termination.
-r [ –restart ] Restart the application.
-t [ –template ] Creates an input file template of the specified name and then exits.
-v [ –validate ] Validate an input file for correctness and then exits.
-o [ –output-file ] arg Specifies output file.
-i [ –input-file ] arg Specifies input file.


Conclusion

In my previous blog post, I discussed how expensive it is to write software. In this case, a simple custom written command line processor could easily cost several thousand dollars to write on your own. Put simply, Boost can save you money. Next time you write an application with command line arguments, I hope you consider using Boost rather than writing your own command line processor.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: