Reputation: 95
void parse_and_run_command(const std::string &command) {
std::istringstream iss(command);
std::istream_iterator<char*> begin(iss), end;
std::vector<char*> tokens(begin, end); //place the arguments in a vector
tokens.push_back(NULL);
According to GDB, the segfault occurs after executing the second line with the istream_iterator. It did not segfault earlier when I was using string vectors.
Upvotes: 1
Views: 445
Reputation: 15265
Hm, very interesting. Sounds like an easy task, but there are several caveats.
First of all, we need to consider that there are at least 2 different implementations of execv
.
One under Posix / Linux, see here and a windows version: see here and here.
Please note the different function signatures:
Linux / POSIX: int execv(const char *path, char *const argv[]);
Windows: intptr_t _execv(const char *cmdname, const char *const *argv);
In this case I find the WIndows version a little bit cleaner, because the argv parameter is of type const char *const *
. Anyway, the major problem is, that we have to call legacy code.
Ok, let's see.
The execv
function requires a NULL-terminated array of char pointers with the argument for the function call. This we need to create.
We start with a std::string
containing the command. This needs to be split up into parts. There are several ways and I added different examples.
The most simple way is maybe to put the std::string
into a std::istringstream
and then to use the std::istream_iterator
to split it into parts. This is the typical short sequence:
// Put this into istringstream
std::istringstream iss(command);
// Split
std::vector parts(std::istream_iterator<std::string>(iss), {});
We use the range constructor for the std::vector
. And we can define the std::vector
without template argument. The compiler can deduce the argument from the given function parameters. This feature is called CTAD ("class template argument deduction").
Additionally, you can see that I do not use the "end()"-iterator explicitely.
This iterator will be constructed from the empty brace-enclosed default initializer with the correct type, because it will be deduced to be the same as the type of the first argument due to the std::vector constructor requiring that.
We can avoid the usage of std::istringstream
and directly convert the string into tokens using std::sregex_token_iterator
. Very simple to use. And the result is a one liner for splitting the original comand string:
// Split
std::vector<std::string> parts(std::sregex_token_iterator(command.begin(), command.end(), re, -1), {});
All this then boils down to 6 lines of code, including the definition of the variable and the invocation of the execv
function:
Please see:
#include <iostream>
#include <string>
#include <sstream>
#include <vector>
#include <iterator>
#include <memory>
#include <algorithm>
#include <regex>
const std::regex re{ " " };
// Define Dummy function for _execv (Windows style, eveything const)
// Note: Type of argv decays to " const char* const* "
int _execv(const char* path, const char* const argv[]) {
std::cout << "\n\nPath: " << path << "\n\nArguments:\n\n";
while (*argv != 0) std::cout << *argv++ << "\n";
return 0;
}
// Define Dummy function for _execv (Posix style)
// Note: Type of argv decays to " char* const* "
int execv(const char* path, char* const argv[]) {
std::cout << "\n\nPath: " << path << "\n\nArguments:\n\n";
while (*argv != 0) std::cout << *argv++ << "\n";
return 0;
}
int main() {
{
// ----------------------------------------------------------------------
// Solution 1
// Initial example
char path[] = "path";
const char* const argv[] = { "arg1", "arg2", "arg3", 0 };
_execv(path, argv);
}
{
// ----------------------------------------------------------------------
// Solution 2
// Now, string, with command convert to a handmade argv array
std::string command{ "path arg1 arg2 arg3" };
// Put this into istringstream
std::istringstream iss(command);
// Split into substrings
std::vector parts(std::istream_iterator<std::string>(iss), {});
// create "argv" List. argv is of type " const char* "
std::unique_ptr<const char*[]> argv = std::make_unique<const char*[]>(parts.size());
// Fill argv array
size_t i = 1U;
for (; i < parts.size(); ++i) {
argv[i - 1] = parts[i].c_str();
}
argv[i - 1] = static_cast<char*>(0);
// Call execv
// Windows
_execv(parts[0].c_str(), argv.get());
// Linux / Posix
execv(parts[0].c_str(), const_cast<char* const*>(argv.get()));
}
{
// ----------------------------------------------------------------------
// Solution 3
// Transform string vector to vector of char*
std::string command{ "path arg1 arg2 arg3" };
// Put this into istringstream
std::istringstream iss(command);
// Split
std::vector parts(std::istream_iterator<std::string>(iss), {});
// Fill argv
std::vector<const char*> argv{};
std::transform(parts.begin(), parts.end(), std::back_inserter(argv), [](const std::string& s) { return s.c_str(); });
argv.push_back(static_cast<const char*>(0));
// Call execv
// Windows
_execv(argv[0], &argv[1]);
// Linux / Posix
execv(argv[0], const_cast<char* const*>(&argv[1]));
}
{
// ----------------------------------------------------------------------
// Solution 4
// Transform string vector to vector of char*. Get rid of istringstream
std::string command{ "path arg1 arg2 arg3" };
// Split
std::vector<std::string> parts(std::sregex_token_iterator(command.begin(), command.end(), re, -1), {});
// Fill argv
std::vector<const char*> argv{};
std::transform(parts.begin(), parts.end(), std::back_inserter(argv), [](const std::string& s) { return s.c_str(); });
argv.push_back(static_cast<const char*>(0));
// Call execv
// Windows
_execv(argv[0], &argv[1]);
// Linux / Posix
execv(argv[0], const_cast<char* const*>(&argv[1]));
}
return 0;
}
Upvotes: 0
Reputation: 12283
First, you need to split the std::string
command into list of tokens of type std::vector<std::string>
. Then, you may want to use std::transform
in order to fill the new list of tokens of type std::vector<char const*>
.
Here is a sample code:
void parse_and_run_command(std::string const& command) {
std::istringstream iss(command);
std::vector<std::string> results(std::istream_iterator<std::string>{iss},
std::istream_iterator<std::string>());
// debugging
for (auto const& token : results) {
std::cout << token << " ";
}
std::cout << std::endl;
std::vector<const char*> pointer_results;
pointer_results.resize(results.size(), nullptr);
std::transform(
std::begin(results), std::end(results),
std::begin(pointer_results),
[&results](std::string const& str) {
return str.c_str();
}
);
// debugging
for (auto const& token : pointer_results) {
std::cout << token << " ";
}
std::cout << std::endl;
// execv expects NULL as last element
pointer_results.push_back(nullptr);
char **cmd = const_cast<char**>(pointer_results.data());
execv(cmd[0], &cmd[0]);
}
Note the last part of the function: execv
expects last element to be nullptr
.
Upvotes: 1
Reputation: 36488
You first need to create a std::vector
of std::string
which will own the string data, you can then transform that std::vector
into a std::vector
of pointers, note that the pointers will only be valid for the lifetime of the std::string
std::vector
:
#include <string>
#include <iostream>
#include <sstream>
#include <iterator>
#include <vector>
#include <algorithm>
void parse_and_run_command(const std::string &command) {
std::istringstream iss(command);
std::istream_iterator<std::string> begin(iss), end;
std::vector<std::string> tokens(begin, end);
std::vector<char*> ctokens;
std::transform(tokens.begin(), tokens.end(), std::back_inserter(ctokens), [](std::string& s) { return s.data(); });
ctokens.push_back(nullptr);
for (char* s : ctokens) {
if (s) {
std::cout << s << "\n";
}
else {
std::cout << "nullptr\n";
}
}
}
int main() {
parse_and_run_command("test test2 test3");
}
Upvotes: 1