Added pipe support for threaded commands via the pipe() function. README updated.

This commit is contained in:
Claudio Maggioni 2016-05-21 18:25:20 +02:00
parent 40f2b2ee6b
commit 025e95c020
10 changed files with 174 additions and 67 deletions

View File

@ -3,10 +3,10 @@ Library that provides a bash-like command line for C++ programs. Features includ
* included flag handling implemented with popt.h;
* creation of personalized commands in the same thread of the shell or in other threads;
* basic shell functionalities, such as execution of other programs in the system. This is implemented with execvp().
* piping, implemented with <unistd.h> pipe() function. Code based on http://stackoverflow.com/a/5207730.
What is not actually included:
* advanced shell features, like:
* piping,
* redirection,
* scripting,
* command history.

View File

@ -34,5 +34,6 @@ if(${lib})
add_library (mshconsole SHARED ${SRC_LIST})
else(${lib})
MESSAGE(STATUS "Building exec (debug only)")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g")
add_executable(${PROJECT_NAME} ${SRC_LIST})
endif(${lib})

View File

@ -43,8 +43,6 @@ namespace mshconsole {
int Command::execute(const struct Params& p, CommandExecutor* c, std::ostream& errorStream){
if(p.argc<=0) return -1;
// options a, b, c take integer arguments
// options f and g take no arguments
poptContext pc;
opts.push_back(POPT_TERMINATOR);

View File

@ -41,7 +41,7 @@ namespace mshconsole{
ExitException(int c=0) : code(c){}
};
public:
virtual int executeCmd(const struct Params& args) = 0;
virtual int executeCmd(const struct Params& args, bool isPipe=false) = 0;
virtual int executeCmd(const std::string& args) = 0;
virtual size_t howManyCmds() const = 0;
void exit(int code=0);

View File

@ -46,6 +46,14 @@ namespace mshconsole {
}
throw CommandNotFoundException();
}
bool Shell::Commands::found(const std::string& s, bool launchThread){
for(unsigned int i=0; i<(launchThread ? threadCommands.size() : commands.size()); i++){
if((launchThread ? threadCommands[i]->getName() : commands[i]->getName())==s){
return true;
}
}
return false;
}
}

View File

@ -65,7 +65,7 @@ int main(int argc, char **argv)
Command cd("cd", &cdExecute);
//add builtin commands
mshConsoleTest.addCmd(&help);
mshConsoleTest.addCmd(&help, true);
mshConsoleTest.addCmd(&cd);
mshConsoleTest.addCmd(&exit);
mshConsoleTest.launch();

View File

@ -61,7 +61,7 @@ namespace mshconsole {
//Run the shell loop.
int exitCode;
std::string* line=nullptr;
struct Params p;
std::vector<std::string> p;
try{
while(1){
@ -88,15 +88,11 @@ namespace mshconsole {
continue;
}
//Execute the command.
executeCmd(p);
//Free p
deleteParams(p);
//Execute the pipe.
executePipe(p);
};
} catch(CommandExecutor::ExitException c) {
delete line;
deleteParams(p);
exitCode=c.getCode();
}
@ -107,6 +103,103 @@ namespace mshconsole {
return exitCode;
}
int Shell::executePipe(std::vector<std::string>& argv)
{
int newPipe[2], oldPipe[2], status;
pid_t pid;
std::vector<std::vector<std::string>> argvs;
size_t c=0, i=0;
for(; i<argv.size(); i++){
if(argv[i]=="|"){ //Pipe reached
if(c!=i){
//Write the command in the pipe std::vector
argvs.push_back(std::vector<std::string>(argv.begin()+c, argv.begin()+i));
}
c=i+1;
}
}
if(c!=i){
//Write the last command in the pipe std::vector
argvs.push_back(std::vector<std::string>(argv.begin()+c, argv.end()));
}
if(argvs.size()==1){
struct Params p;
vectorToArgcArgv(argvs[0], &p.argc, &p.argv);
return executeCmd(p,false);
}
for(size_t i=0; i<argvs.size(); i++){
if(cmds.found(std::string(argvs[i][0]),false)){
errorStream << name << ": non-thread command " << argv[0] << " can't be launched from a pipe" << std::endl;
return 1;
}
}
for(size_t i=0; i<argvs.size(); i++) /* For each command */
{
struct Params p;
vectorToArgcArgv(argvs[i], &p.argc, &p.argv);
/* If there still are commands to be executed */
if(i < argvs.size()-1)
{
pipe(newPipe); /* just create a pipe */
}
pid = fork();
if(pid == 0) /* Child */
{
/* If there is a previous command */
if(i > 0)
{
close(oldPipe[1]);
dup2(oldPipe[0], 0); //redirect stdin to input of the pipe
close(oldPipe[0]);
}
/* If there still are commands to be executed */
if(i < argvs.size()-1)
{
close(newPipe[0]);
dup2(newPipe[1], 1); //redirect stdout to output of the pipe
close(newPipe[1]);
}
//Execute
exit(executeCmd(p,true));
}
else if (pid < 0) {
// Error forking
errorStream << name <<": error in process forking.\n";
}
else /* Father */
{
/* If there is a previous command */
if(i > 0)
{
close(oldPipe[0]);
close(oldPipe[1]);
}
/* do we have a next command? */
if(i < argvs.size()-1)
{
oldPipe[0] = newPipe[0];
oldPipe[1] = newPipe[1];
}
/* wait for last command process? */
if(i == argvs.size()-1)
{
waitpid(pid, &status, 0);
}
}
deleteParams(p.argc,p.argv);
}
return 0;
}
int Shell::launchCmd(const struct Params& p){
int status;
@ -146,21 +239,29 @@ namespace mshconsole {
return 1;
}
int Shell::executeCmd(const struct Params& p){
int Shell::executeCmd(const struct Params& p, bool isPipe){
if (!p.argc) return 1; //Return if the line is empty.
try {
//Search in non-thread commands.
return cmds.launch(p, false);
if(isPipe){
if(cmds.found(p.argv[0],false)){
errorStream << name << ": non-thread command " << p.argv[0] << " can't be launched from a pipe" << std::endl;
return 1;
}
}
else{
try {
//Search in non-thread commands.
return cmds.launch(p, false);
}
catch(CommandNotFoundException) {}
}
catch(CommandNotFoundException) {}
//Execute the threadCommand or the executable.
return launchCmd(p);
}
inline int Shell::executeCmd(const std::string &args){
return executeCmd(splitLine(&args));
std::vector<std::string>a =splitLine(&args);
return executePipe(a);
}
void Shell::SIGINTHandler(int,siginfo_t*, void *){
@ -217,10 +318,8 @@ namespace mshconsole {
return buffer;
}
struct Params Shell::splitLine(const std::string* line){
struct Params p;
stringToArgcArgv(*line, &(p.argc), &(p.argv));
return p;
std::vector<std::string> Shell::splitLine(const std::string* line){
return parseStringToVector(*line);
}
void Shell::deleteParams(struct Params p){

View File

@ -55,6 +55,7 @@ namespace mshconsole {
void add(Command*, bool isthread=false);
size_t howMany() const;
int launch(const struct Params& args, bool launchThread=false);
bool found(const std::string& args, bool launchThread=false);
};
static bool SIGINTRaised;
@ -70,7 +71,8 @@ namespace mshconsole {
int launchCmd(const struct Params& args);
static void SIGINTHandler(int,siginfo_t *info, void *);
std::string* readLine();
struct Params splitLine(const std::string* line);
std::vector<std::string> splitLine(const std::string* line);
int executePipe(std::vector<std::string>& argv);
void deleteParams(struct Params p);
class IsUndoingLineException {};
@ -92,7 +94,7 @@ namespace mshconsole {
//for in-shell commands
void addCmd(Command *cmd, bool isThread=false);
int executeCmd(const struct Params& args);
int executeCmd(const struct Params& args, bool isPipe=false);
int executeCmd(const std::string& args);
size_t howManyCmds() const;
};

View File

@ -27,30 +27,28 @@
#include "stringtoargcargv.h"
namespace StringToArgcArgv{
/*
* Usage:
* int argc;
* char** argv;
* stringToArgcArgv("foo bar", &argc, &argv);
*/
void stringToArgcArgv(const std::string& str, int* argc, char*** argv)
{
std::vector<std::string> args = parse(str);
*argv = new char*[args.size()+1]();
static inline bool _isQuote(char c);
static inline bool _isEscape(char c);
static inline bool _isPipe(char c);
static inline bool _isWhitespace(char c);
int i=0;
for(std::vector<std::string>::iterator it = args.begin();
void vectorToArgcArgv(const std::vector<std::string>& args, int* argc, char*** argv){
*argv = new char*[args.size()+1]();
int i=0;
for(std::vector<std::string>::const_iterator it = args.begin();
it != args.end();
++it, ++i)
{
std::string arg = *it;
(*argv)[i] = new char[arg.length()+1]();
std::strcpy((*argv)[i], arg.c_str());
}
(*argv)[i]=NULL;
++it, ++i){
std::string arg = *it;
(*argv)[i] = new char[arg.length()+1]();
std::strcpy((*argv)[i], arg.c_str());
}
(*argv)[i]=NULL;
*argc = args.size();
}
*argc = args.size();
void stringToArgcArgv(const std::string& str, int* argc, char*** argv){
return vectorToArgcArgv(parseStringToVector(str),argc,argv);
}
void freeString(int& argc, char**& argv){
@ -60,7 +58,7 @@ namespace StringToArgcArgv{
delete[] argv;
}
std::vector<std::string> parse(const std::string& args)
std::vector<std::string> parseStringToVector(const std::string& args)
{
std::stringstream ain(args); // used to iterate over input string
ain >> std::noskipws; // do not skip white spaces
@ -101,19 +99,21 @@ namespace StringToArgcArgv{
}
}
else if(_isWhitespace(c)) {
switch(currentState) {
else if(_isWhitespace(c) || _isPipe(c)) {
switch(currentState) {
case InArg:
oargs.push_back(currentArg.str());
currentState = OutOfArg;
break;
oargs.push_back(currentArg.str());
if(_isPipe(c)) oargs.push_back("|");
currentState = OutOfArg;
break;
case InArgQuote:
currentArg << c;
break;
currentArg << c;
break;
case OutOfArg:
// nothing
break;
}
if(_isPipe(c)) oargs.push_back("|");
// nothing
break;
}
}
else if(_isEscape(c)) {
switch(currentState) {
@ -174,8 +174,7 @@ namespace StringToArgcArgv{
return oargs;
}
bool _isQuote(char c)
{
static inline bool _isQuote(char c){
if(c == '\"')
return true;
else if(c == '\'')
@ -184,16 +183,14 @@ namespace StringToArgcArgv{
return false;
}
bool _isEscape(char c)
{
static inline bool _isEscape(char c){
if(c == '\\')
return true;
return false;
}
bool _isWhitespace(char c)
{
static inline bool _isWhitespace(char c){
if(c == ' ')
return true;
else if(c == '\t')
@ -201,4 +198,9 @@ namespace StringToArgcArgv{
return false;
}
static inline bool _isPipe(char c){
if(c == '|') return true;
return false;
}
}

View File

@ -36,11 +36,8 @@
#include <cstring>
namespace StringToArgcArgv{
bool _isQuote(char c);
bool _isEscape(char c);
bool _isEscape(char c);
bool _isWhitespace(char c);
std::vector<std::string> parse(const std::string& args);
void vectorToArgcArgv(const std::vector<std::string>& args, int* argc, char*** argv);
std::vector<std::string> parseStringToVector(const std::string& args);
void stringToArgcArgv(const std::string& str, int* argc, char*** argv);
void freeString(int& argc, char**& argv);
}