I have a windows command line program using Boost.Program_Options. One option uses a std::filesystem::path variable.
namespace fs = std::filesystem;
namespace po = boost::program_options;
fs::path optionsFile;
po::options_description desc( "Options" );
desc.add_options()
("help,h", "Help screen")
("options,o", po::value<fs::path>( &optionsFile ), "file with options");
calling the program with -o c:\temp\options.txt or with -o "c:\temp\options.txt" works fine, but calling the program with -o "c:\temp\options 1.txt" fails with this error:
error: the argument( 'c:\temp\options 1.txt' ) for option '--options' is invalid
The content of argv in this case is:
- argv[0] = Exepath
- argv[1] = -o
- argv[2] = c:\temp\options 1.txt
This is the full code:
#include <boost/program_options.hpp>
#include <filesystem>
#include <iostream>
namespace fs = std::filesystem;
namespace po = boost::program_options;
int wmain( int argc, wchar_t * argv[] )
{
try
{
fs::path optionsFile;
po::options_description desc( "Options" );
desc.add_options()
("help,h", "Help screen")
("options,o", po::value<fs::path>( &optionsFile ), "File containing the command and the arguments");
po::wcommand_line_parser parser{ argc, argv };
parser.options( desc ).allow_unregistered().style(
po::command_line_style::default_style |
po::command_line_style::allow_slash_for_short );
po::wparsed_options parsed_options = parser.run();
po::variables_map vm;
store( parsed_options, vm );
notify( vm );
if( vm.count( "help" ) )
{
std::cout << desc << '\n';
return 0;
}
std::cout << "optionsFile = " << optionsFile << "\n";
}
catch( const std::exception & e )
{
std::cerr << "error: " << e.what() << "\n";
return 1;
}
return 0;
}
How can I handle paths containing whitespace correctly? Is that even possible using std::filesystem::path or do I have to use std::wstring?
Indeed I could reproduce this. Replacing
fs::pathwithstd::stringfixed it.Here's a side-by-side reproducer: Live On Coliru
Prints
The reason most likely is that extraction from the command line argument defaults to using
operator>>on a stringstream¹. If that hasskipwsset (as all C++ istreams do by default), then whitespace stops the "parse" and the argument is rejected because it is not fully consumed.However, modifying the code to include a
validateoverload that fires forpaths, addingstd::noskipwsdidn't help!Apparently,
operator>>forfs::pathdoesn't obeynoskipws. A look at the docs confirms:This gives us the workaround:
Workaround
Here we balance the
std::quotedquoting/escaping as required.Live Demo
Proof Of Concept:
Live On Coliru
Now prints
¹ this actually happens inside
boost::lexical_castwhich comes from Boost Conversion