1

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
Clint Chelak
  • 232
  • 2
  • 9
  • 3
    Please make a [mre]. Sidenote: It's probably not `boost` that nukes the `""`, it's likely the shell (if you're using `sh` or any shell derived from it). – Ted Lyngmo Apr 15 '21 at 05:23
  • "boost::program_options seems to nuke quotes" - no. Shells nuke quotes. The quotes only serve to tell the shell which parameters are single parameters, they're not actually part of the the command line argument. If you want that, you have to make it `myprogram "\"with 'quotes'\""` – sehe Apr 15 '21 at 12:19
  • I added the minimal example. I also changed the wording to not blame boost for removing quotes. Thanks for the clarification. I think the simplest solution that I might answer is to add an intermediate parsing step to look for a string, character or token that will be parsed as an empty string. A SegFault is a mean punishment, though. boost should make my mistake an exception. – Clint Chelak Apr 15 '21 at 20:17

0 Answers0