C ++: how to split (not parse) a string into command line arguments?

I use boost::program_options to parse my command line. Now I am adding support for batch execution with the --script argument, denoting a file containing command line options on each line, for example:

 --src="\"z:\dev\veds\sqlexpress\Run 1.ved\"" --src-kind=bla --yz --src=z:\dev\veds\sqlexpress\db.ebf --src=z:\dev\veds\sqlexpress\db2.mdf --src=db3 --src="\"z:\dev\veds\sqlite\Run 41 (Run 23).ved\"" --src=z:\dev\veds\sqlite\ws_results_db_2012_01_15_18_37_03.db3 --src=z:\dev\veds\mysql\10.ved --src=z:\dev\veds\mysql\db 

Each line in the file indicates one run of my tool and lists the command line options for that particular run.

The problem is that reading the script file gives complete lines that do not break into individual command line parameters. But to use boost::program_options you need to have argc and argv , that is, from someone you need to split the command line into different parameters.

I can't just break the spaces because some values ​​contain spaces and therefore they are enclosed in double quotes, even nested double quotes.

On the other hand, I don’t want to run the tool from the OS command line for each set of command line parameters because of the expensive bootstrap - the reason I present the script function in the first place.

Is there an easy way to break strings into command line arguments just like the OS does?

Thanks.

+1
source share
3 answers

OK I understood. Here is my code:

  string script; { ifstream file(scriptPath.c_str()); file.seekg(0, ios::end); script.resize(file.tellg()); file.seekg(0, ios::beg); file.read(const_cast<char *>(script.c_str()), script.size()); } boost::replace_all(script, "\\", "\\\\"); // Escape the backslashes boost::replace_all(script, "\\\\\"", "\\\""); // Except for those escaping the quotes boost::trim_right_if(script, is_space_or_zero); // There are extra '\0' in the string, because the file is read as text, but its length was computed as binary vector<string> lines; boost::split(lines, script, boost::is_any_of("\n")); // I prefer getting a string line iterator here, the question is how? escaped_list_separator<char> sep('\\', ' ', '"'); int res = 0; BOOST_FOREACH (const string& line, lines) { // reset the command line variables here, since this is like a new execution // Tokenize the command line, respecting escapes and quotes tokenizer<escaped_list_separator<char>> tok(line, sep); vector<string> args(tok.begin(), tok.end()); po::variables_map vm; po::store(po::command_line_parser(args).options(options).run(), vm); res += run(vm); } 

I use http://www.boost.org/doc/libs/1_48_0/libs/tokenizer/ to break the lines. It works very well.

+1
source

The Boost documentation includes answer files , including a simple example of their use. This will be close to what you want, except that they also say that it “has some limitations” that include parsing spaces.

They also have parse_config_file () , which will load the parameters from the file. Here you refuse the identical syntax in the file, as on the command line, and their included implementation will (simply) support one “command invocation” for each program invocation. But I'm sure you could see how they do it, and copy some of this code. If I were you, I could configure it to support .ini syntax as follows:

 [job1] src=z:\dev\veds\sqlexpress\Run 1.ved src-kind=bla y= z= [job2] src=z:\dev\veds\sqlexpress\db.ebf [another_job] src=z:\dev\veds\sqlexpress\db2.mdf 

I am sure that this is not a terrible amount of additional work, and this gives one more advantage of having an explicit name for each work you do. Boost own parse_config_file () uses section names (in brackets) as a prefix for option names, but this is not necessary, so you can also reprofile them in the interest of preserving the simple .ini syntax.

Edit: do you need something simpler? OK. Abandon the idea of ​​having identical syntax in the file, as on the command line, with respect to quoting and spaces. Define a suitable delimiter, for example ; between options if you have to maintain spaces in your arguments. Rotating something like this into argc / argv pairs should be simple enough using std :: string :: find () or Boost Tokenizer:

 --src=z:\dev\veds\sqlexpress\Run 1.ved; --src-kind=bla; --yz --src=z:\dev\veds\sqlexpress\db.ebf --src=z:\dev\veds\sqlexpress\db2.mdf 

Divide by ; to make argv [1,2,3] in the first example and copy your own argv [0] program to the "fake" argv [0], then parse the parameters with Boost.

0
source

Check this out :: C ++ Cookbook Line Split (Recipe 4.6)

Example 4-10 Split a line with delimiters

 #include <string> #include <vector> #include <functional> #include <iostream> using namespace std; void split(const string& s, char c, vector<string>& v) { string::size_type i = 0; string::size_type j = s.find(c); while (j != string::npos) { v.push_back(s.substr(i, ji)); i = ++j; j = s.find(c, j); if (j == string::npos) v.push_back(s.substr(i, s.length( ))); } } int main( ) { vector<string> v; string s = "Account Name|Address 1|Address 2|City"; split(s, '|', v); for (int i = 0; i < v.size( ); ++i) { cout << v[i] << '\n'; } } 

-

 template<typename T> void split(const basic_string<T>& s, T c, vector<basic_string<T> >& v) { basic_string<T>::size_type i = 0; basic_string<T>::size_type j = s.find(c); while (j != basic_string<T>::npos) { v.push_back(s.substr(i, ji)); i = ++j; j = s.find(c, j); if (j == basic_string<T>::npos) v.push_back(s.substr(i, s.length( ))); } } 

Example 4-11 Splitting a string using Boost

 #include <iostream> #include <string> #include <list> #include <boost/algorithm/string.hpp> using namespace std; using namespace boost; int main( ) { string s = "one,two,three,four"; list<string> results; split(results, s, is_any_of(",")); // Note this is boost::split for (list<string>::const_iterator p = results.begin( ); p != results.end( ); ++p) { cout << *p << endl; } } 

-

 template<typename Seq, typename Coll, typename Pred> Seq& split(Seq& s, Coll& c, Pred p, token_compress_mode_type e = token_compress_off); 

I can’t share the text (it’s illegal to copy / paste from a book), but these examples are quite explanatory. If you want to see the text, you will need to specify the book.

These two examples were taken from recipe 4.6.

C ++ cookbook

Jeff Cogswell, Christopher Diggins, Ryan Stevens, Jonathan Turkanis

Publisher: O'Reilly

Publication Date: November 2005

ISBN: 0-596-00761-2

0
source

Source: https://habr.com/ru/post/1414996/


All Articles