views:

1258

answers:

8

I have an std::string containing a command to be executed with execv, what is the best "C++" way to convert it to the "char *argv[]" that is required by the second parameter of execv()?

To clarify:

std::string cmd = "mycommand arg1 arg2";
char *cmd_argv[];

StrToArgv(cmd, cmd_argv); // how do I write this function?

execv(cmd_argv[0], cmd_argv);
A: 

You can use the c_str() function of std::string to convert to char*. The strtok function will split the string using the ' ' delimiter.

Patrice Bernassola
You have to split the string by spaces before passing to `execv`. It requires arguments to be passed as separate items in the array.
Mehrdad Afshari
Yeah that's right. I did not have the code example when writing the answer.
Patrice Bernassola
+1  A: 

A combination of the c_str() string method and strtok() to split it up by spaces should get you the array of strings you need to pass to exec() and its related functions.

Carl Norum
This might be fine if you don't intend to supoprt quoting to allow a single argument to contain spaces.
Michael Burr
Well, yeah you have to be careful. Quoting is a whole new can of worms.
Carl Norum
A: 

Perhaps split_winmain from Boost.ProgramOptions. Boost is a good choice in most cases. http://www.boost.org/doc/libs/1_40_0/doc/html/program_options/howto.html#id1396212

If you are only interested in Windows (other kernels generally don't know about command lines in the Windows sense), you can use the API function CommandLineToArgvW which uses the same conventions as the MS C runtime.

In general it depends on the quoting style of the platform and/or the shell. The Microsoft C Runtime uses a quite different style than e.g. bash!

Philipp
+6  A: 

Very non-unixy answers here. What's wrong with:

std::string cmd = "echo hello world";
execl("/bin/sh", "/bin/sh", "-c", cmd.c_str(), NULL);

Why bother writing a command line parser when there's a perfectly good one already on the system?

(Note: one good reason is because you don't trust the string you're about to execute. One hopes that this is already true, but the shell will do "more" with that string than a naive whitespace-splitter will and thus open more security holes if you aren't careful.)

Andy Ross
What if there isn't one on the system?
Carl Norum
A system with the execve() system call but no shell? Never heard of such a beast.
Andy Ross
when I run a test program with only the two lines above, and cmd = "echo hello world", the execl call does not succeed, it returns. The errno is 2. when I change the first argument from "sh" to "/bin/sh", it gives me the error "-c: echo hello world: No such file or directory"
aaronstacy
Indeed, my answer was typed without testing. The first argument needs to be the command name, corresponding to argv[0], not the first argument itself.
Andy Ross
+1  A: 
std::vector<char *> args;
std::istringstream iss(cmd);

std::string token;
while(iss >> token) {
  char *arg = new char[token.size() + 1];
  copy(token.begin(), token.end(), arg);
  arg[token.size()] = '\0';
  args.push_back(arg);
}
args.push_back(0);

// now exec with &args[0], and then:

for(size_t i = 0; i < args.size(); i++)
  delete[] args[i];

Of course, this won't work with commans that use quoting like rm "a file.mp3". You can consider the POSIX function wordexp which cares about that and much more.

Johannes Schaub - litb
Commands with quoting are exactly why execv takes an array - to avoid making the decision what the quoting rules should be. Unless the questioner says what quoting rules he wants, we can't answer the question, and by the time he's properly specified them, the answer will be "generate a parser for the grammar you have just specified" ;-)
Steve Jessop
@onebyone true, probably at some time in the program, he already had the arguments in an array - we don't know. But if he has just a string, and he has to do something like splitting it for some purpose like logging or auditing, it's always useful to know about `wordexp` etc. I agree that in general, just pushing to `sh` is a good idea.
Johannes Schaub - litb
A: 

Matt Peitrek's LIBTINYC has a module called argcargv.cpp that takes a string and parses it out to the argument array taking quoted arguments into account. Note that it's Windows-specific, but it's pretty simple so should be easy to move to whatever platform you want.

If you do that, also change it to take as parameters the loaction to put the count and the a pointer to the argv array instead of using externs (just my little bit of advice). Matt didn't need that because LIBTINYC was the runtime.

Alternatively, you can look in your compiler's runtime source (nearly all provide it) to see what they do to parse the commandline and either call that directly (if that turns out to be workable) or borrow the ideas from that bit of code.

Michael Burr
+1  A: 

This is a variation on litb's answer, but without all the manual memory allocation. It still won't handle quoting.

#include <vector>
#include <string>
#include <sstream>

std::string cmd = "mycommand arg1 arg2";
std::istringstream ss(cmd);
std::string arg;
std::vector<std::string> v1;
std::vector<char*> v2;
while (ss >> arg)
{
   v1.push_back(arg); 
   v2.push_back(const_cast<char*>(v1.back().c_str()));
}
v2.push_back(0);  // need terminating null pointer

execv(v2[0], &v2[0]);

I feel kind of dirty about the const_cast<>, but programs really shouldn't be modifying the contents of the argv strings.

Brian Neal
A: 
Bruce