Summary
I've seen this question, that asks and answers how to pass a no-op for a defined value. However, how do you pass in an empty string to an object from command-line, especially for unregistered options? And how can I avoid boost segmentation faults as a result of passing in an empty string/value?
Details
For example, I have options that look like the following. They are deemed unregistered by the program's main entry-point. They are then passed down to another library to parse.
# These are equivalent, with and without quotes
--MyLibrary.myCsv="one,two,three"
--MyLibrary.myCsv=one,two,three
However, this option can be blank, so something like:
--MyLibrary.myCsv=""
Now, since the quotes are removed by the shell, this throws a SegFault (for versions earlier than boost 1.67, at least). To be explicit, I get an exception followed by the SegFault message "Aborted (core dumped)":
terminate called after throwing an instance of 'boost::exception_detail::clone_impl<boost::exception_detail::error_info_injector<boost::program_options::invalid_command_line_syntax> >'
what(): the argument for option '--MyLibrary.myCsv' should follow immediately after the equal sign
(core dumped)
I can write it like this:
--MyLibrary.myCsv=\"\"
And it won't seg fault, but humorously, it will sets the value to "\"\"", which is clearly not an empty string... Is there a way to just give it an empty string for value?
Example Code
Here is a small example (hopefully) showcasing my problem. I called it emptyQuotes.cpp
#include <iostream>
#include <vector>
#include <string>
#include <boost/program_options.hpp>
using namespace std;
namespace po = boost::program_options;
/**
* @brief parser for another (potentially external) library.
*
* For simplicity, it looks for '=' to split key from value
*/
void libraryParser( const vector<string>& options )
{
for ( auto it = options.begin(); it != options.end(); ++it )
{
size_t eqPos = it->find('=');
size_t start = it->find_first_not_of('-');
string key = it->substr(start, eqPos-start);
string val = it->substr(eqPos + 1);
cout << " key = \"" << key << "\"; val = \"" << val << "\"\n";
cout << " Is val empty? ";
if(val.empty())
cout << "yes";
else
cout << "no";
cout << "\n";
}
}
int main (int argc, char** argv)
{
cout << "Command line:\n ";
for ( int ii = 0; ii < argc; ++ii )
cout << argv[ii] << " ";
cout << "\n";
// Commands that the basic entry point would know
boost::program_options::options_description basic("Basic");
basic.add_options()
( "help,h", "produce a help message" );
po::parsed_options parsed = po::command_line_parser(argc, argv)
.options(basic)
.allow_unregistered() // <-I don't know how to handle blank values here
.run();
po::variables_map vm;
po::store(parsed, vm);
vector<string> unregOptions = po::collect_unrecognized( parsed.options, po::exclude_positional );
libraryParser(unregOptions);
cout << "\n";
return 0;
}
Running the following got the SegFault:
./emptyQuotes --MyLibrary.myVal=""
Command line:
./emptyQuotes --MyLibrary.myVal=
terminate called after throwing an instance of 'boost::exception_detail::clone_impl<boost::exception_detail::error_info_injector<boost::program_options::invalid_command_line_syntax> >'
what(): the argument for option '--MyLibrary.myVal' should follow immediately after the equal sign
Aborted (core dumped)
And it reported the value wasn't empty with the slashes:
./emptyQuotes --MyLibrary.myVal=\"\"
Command line:
./emptyQuotes --MyLibrary.myVal=""
key = "MyLibrary.myVal"; val = """"
Is val empty? no