Reputation: 1090
I am writing a command shell in C++ using the POSIX api, and have hit a snag. I am executing via execvp(3), so I somehow need to turn the std::string that contains the command into a suitable array of char* consts*'s that can be passed to:
int execvp(const char *file, char *const argv[]);
I have been racking my brain for hours but I can't think of any realistic or sane way to do this. Any help or insight on how I can achieve this conversion would be greatly appreciated. Thank you and have a good day!
edit: As per request of Chnossos, here is an example:
const char *args[] = {"echo", "Hello,", "world!"};
execvp(args[0], args);
Upvotes: 1
Views: 1254
Reputation: 129464
Assuming you have a string that contains more than "one argument", you will first have to split the string (using a std::vector<std::string>
would work to store the separate strings), then for each element in the vector, store the .c_str()
of that string into a const char args[MAXARGS]
[or a std::vector<const char*> args;
and use args.data()
if you don't mind using C++11]. Do not forget to store a 0
or nullptr
in the last element.
It is critical if you use c_str
that the string you are basing that of is not a temporary: const char* x = str.substr(11, 33).c_str();
will not give you the thing you want, because at the end of that line, the temporary string is destroyed, and its storage freed.
If you have only one actual argument,
const char* args[2] = { str.c_str(), 0 };
would work.
Upvotes: 5
Reputation: 1031
So the problem is the splitting of the line into individual arguments, and filling the argument vector with the respective pointers?
Assuming you want to split at the whitespace in the line, you replace whitespace in the string with null-bytes (in-place). You can then fill the argument vector with pointers into the string.
You will have to write a single loop to go through the string.
Upvotes: 0
Reputation: 10486
If the parsing can actually be really complicated, I'd go with something like that:
std::string cmd = "some really complicated command here";
char * const args[] =
{
"sh",
"-c",
cmd.c_str(),
(char *) NULL
};
execvp(args[0], args);
Upvotes: 0
Reputation: 27375
I use this:
command_line.hpp:
#pragma once
#include <vector>
#include <string>
namespace wpsc { namespace unittest { namespace mock {
class command_line final
{
public:
explicit command_line(std::vector<std::string> args = {});
explicit command_line(int argc, char const * const * const argv);
int argc() const;
/// @remark altering memory returned by this function results in UB
char** argv() const;
std::string string() const;
private:
std::vector<std::string> args_;
mutable std::vector<char*> c_args_;
};
}}} // wpsc::unittest::mock
command_line.cpp:
#include <wpsc/unittest/mock/command_line.hpp>
#include <algorithm>
#include <sstream>
namespace wpsc { namespace unittest { namespace mock {
command_line::command_line(std::vector<std::string> args)
: args_( std::move(args) ), c_args_( )
{
}
command_line::command_line(int argc, char const * const * const argv)
: command_line{ std::vector<std::string>{ argv, argv + argc } }
{
}
int command_line::argc() const
{
return static_cast<int>(args_.size());
}
char ** command_line::argv() const
{
if(args_.empty())
return nullptr;
if(c_args_.size() != args_.size() + 1)
{
c_args_.clear();
using namespace std;
transform(begin(args_), end(args_), back_inserter(c_args_),
[](const std::string& s) { return const_cast<char*>(s.c_str()); }
);
c_args_.push_back(nullptr);
}
return c_args_.data();
}
std::string command_line::string() const
{
using namespace std;
ostringstream buffer;
copy(begin(args_), end(args_), ostream_iterator<std::string>{ buffer, " " });
return buffer.str();
}
}}} // wpsc::unittest::mock
Client code:
int main(int argc, char** argv)
{
wpsc::unittest::mock::command_line cmd1{ argc, argv };
// wpsc::unittest::mock::command_line cmd2{ {"app.exe" "-h"} };
some_app_controller c;
return c.run(cmd1.argc(), cmd1.argv());
}
Upvotes: 0
Reputation: 13988
Examplary approach:
#include <string>
#include <vector>
#include <cstring>
using namespace std;
int execvp(const char *file, char *const argv[]) {
//doing sth
}
int main() {
string s = "echo Hello world!";
char* cs = strdup(s.c_str());
char* lastbeg = cs;
vector<char *> collection;
for (char *itcs = cs; *itcs; itcs++) {
if (*itcs == ' ') {
*itcs = 0;
collection.push_back(lastbeg);
lastbeg = itcs + 1;
}
}
collection.push_back(lastbeg);
for (auto x: collection) {
printf("%s\n", x);
}
execvp("abc.txt", &collection[0]);
}
Notice that the memory for the cs
isn't freed here... in your application you would need to take care of that...
The number of elements in array can be simply extracted from collection.size()
Upvotes: 0
Reputation: 182829
You need to decide what the rules will be for your shell and implement them. That's a significant fraction of the work of making a shell.
You need to write this code, and it's not simple. In a typical shell, echo "Hello world!"
has to become { echo
, Hello world!
}, while echo \"Hello world!\"
has to become { echo
, "Hello world!"
}. And so on.
What will "
do in your shell? What will '
do? You need to make these decision before you code this part.
Upvotes: -2