Mini-Shell with pipeline

来源:互联网 发布:淘宝付款显示系统异常 编辑:程序博客网 时间:2024/05/22 05:32

main.cpp

#include <iostream>#include <string>#include <vector>#include <unistd.h>#include <sys/types.h>#include <sys/wait.h>#include <stdlib.h>#include <fcntl.h>using namespace std;#include "shell_lib.h"int main(){string user_input = "";do{/*Display prompt and read a line*/print_prompt();getline(cin,user_input);/*handling pipe line*/vector<string> command_list = split_string(user_input,"|");int size = (int)command_list.size();if (size==0)//filter check: The user just press enter with no commandcontinue;else if (size==1)//handling command without pipe{/*Tokenize the command*/vector<string> tokenized_user_input = tokenize(command_list[0]);/*Handle wildcards*/handle_wildcards(tokenized_user_input);if (tokenized_user_input.size()==0)//filter check: The user just press enter with no commandcontinue;/*Handle built-in command and special command like shell exit*/if (handle_built_in_command(tokenized_user_input))continue;if (exit_shell_cmd(tokenized_user_input))exit(0);//exit the shell process in a normal way/*Execute the command*/int command_exit_status = execute_command_core(tokenized_user_input);/*Check the exit status and report*/check_and_report_failure(command_exit_status);}else if (size>1)//indicates command containing at least one pipe and two seperate command{int fd_read_from_previous=0;//saves the file descriptor in previous commandfor (int command_index=0;command_index<size;command_index++)//for every single command{/*Tokenize the command*/vector<string> tokenized_user_input = tokenize(command_list[command_index]);if (tokenized_user_input.size()==0)//meaning it is an empty commandcontinue;/*Handle wildcards*/handle_wildcards(tokenized_user_input);/*Main process is the manager handling pipe, all the command will be handled in child process one after another*/int pipefd[2];if (pipe(pipefd) == -1) {cerr<<"pipe create error!"<<endl;exit(EXIT_FAILURE);}   int child_pid;int child_status = -1;child_pid = fork();if (child_pid==-1)//fork fails{cerr<<"Fail to fork!"<<endl;exit(EXIT_FAILURE);}else if (child_pid==0) //meaning it is the child process{vector<string> clean_command;//saves commands which erase the I/O redirection partshandle_io_redirection(tokenized_user_input,clean_command);/*Handling pipe,needed to be done after io_redirection handled to overwrite io_redirection.  It is different when:  1. it is the first command in a command list  2. it is the last command in a command list  3. other command in the command list*/if (command_index==0)//meaning it is the first command in a command list, redirect output to pipe{dup2(pipefd[1],1);fd_read_from_previous=pipefd[1];}else if (command_index==size-1)//meaning it is the last command in a command list, redirect input to fd_read_from_previous{dup2(fd_read_from_previous,0);}else//redirect input to fd_read_from_previous and output to pipe{dup2(fd_read_from_previous,0);dup2(pipefd[1],1);fd_read_from_previous=pipefd[1];}/*Handle built-in command and special command like shell exit*/if (handle_built_in_command(clean_command))continue;if (exit_shell_cmd(clean_command))exit(0);//exit the shell process in a normal wayvector<char *> execvp_argument;generate_argument(clean_command,execvp_argument);execvp(clean_command[0].c_str(),&execvp_argument[0]);cerr<<"Command not found!"<<endl;//should not execute this line unless execvp failsexit(EXIT_FAILURE);}else//meaning it is the parent process{child_pid = wait(&child_status);//the parent process wait the child process to finish and fetch the exit_status/*Check the exit status and report*/check_and_report_failure(child_status);//if the previous process end it will close its holding fds, if the parent closes them again, pipefd may not be used in next command//so we comment this two lines.//close(pipefd[0]);//Close unused read end//close(pipefd[1]);//Close unused write end}}}else{cerr<<"Command list size<0!"<<size<<endl;//The program should never execute to here}}while (true);return 0;}


shell_lib.h

#ifndef SHELL_LIB_H#define SHELL_LIB_H#include <iostream>#include <string>#include <vector>#include <glob.h>using namespace std;/*Functions used by main function*///=========================================void print_prompt();/*Tokenized user_input by white space*/vector<string> tokenize(string user_input);/*Handle wildcards and make changes on tokenized_user_input*/void handle_wildcards(vector<string> & tokenized_user_input);/*Input: user_inputDetermine if user_input indicate exiting the shellIf so, return true, otherwise return false */bool exit_shell_cmd(const vector<string> & handled_user_input);/*Execute the command using tokenized commandReturn command execution's return value.*/int execute_command_core(const vector<string> & tokenized_command);/*Check command_exit_status and report according to the command_exit_status if encounter with failureReference to http://tldp.org/LDP/abs/html/exitcodes.html*/void check_and_report_failure(int command_exit_status);/*If tokenized_user_input is a built-in command, handle it and return trueOtherwise return false*/bool handle_built_in_command(const vector<string> & tokenized_user_input);//=========================================/*Function library*///=========================================/*split the str with delimiter, return the result in vector<string>*/vector<string> split_string(const string& str,const string& delimiter);/*Generate the argument command char* array used by system call execvp, which must end with NULL element*/void generate_argument(const vector<string> & tokenized_command,vector<char *> & execvp_argument);/*Two tasks for this function:1. Handle the I/O redirection using dup2 to manipulate the file descriptors2. Generate a clean_command to be executed which erase the i/O redirection tokens*/void handle_io_redirection(const vector<string> & tokenized_command,vector<string> & clean_command);/*These two functions do the similar things:1. Determine if the token is an I/O redirection token2. If it is, manipulate the file descriptor accordingly and return true, otherwise return false*/bool handle_input_redirection(const string & token);bool handle_output_redirection(const string & token);/*function used to handle wildcards*/inline std::vector<std::string> glob(const std::string& pat){    glob_t glob_result;    glob(pat.c_str(),GLOB_TILDE,NULL,&glob_result);    vector<string> ret;    for(unsigned int i=0;i<glob_result.gl_pathc;++i){        ret.push_back(string(glob_result.gl_pathv[i]));    }    globfree(&glob_result);    return ret;}//=========================================#endif


shell_lib.cpp

#include <iostream>#include <string>#include <vector>#include <unistd.h>#include <sys/types.h>#include <sys/wait.h>#include <stdlib.h>#include <fcntl.h>using namespace std;#include "shell_lib.h"/*Functions used by main function*///=========================================void print_prompt(){cout<<"psh>";}/*Tokenized user_input by white space*/vector<string> tokenize(string user_input){return (split_string(user_input," "));}/*Handle wildcards and make changes on tokenized_user_input*/void handle_wildcards(vector<string> & tokenized_user_input){/*std::vector<string>::iterator iter = tokenized_user_input.begin();while (iter!=tokenized_user_input.end()){int str_length = (int)(*iter).length();if (str_length==0)continue;Do not handle wildcards in "..." and '...'char first_char = tokenized_user_input[i][0];char last_char = tokenized_user_input[i][str_length-1];if ((first_char=='"' && last_char=='"') || (first_char=='\'' && last_char=='\''))continue;vector<string> wildcard_result = glob((*iter));if (wildcard_result.size()==1)*iter=wildcard_result[0];else if (wildcard_result.size()>1){?*iter=wildcard_result[0];iter = tokenized_user_input.insert(iter,wildcard_result.begin()+1,wildcard_result.end());iter+=(wildcard_result.size*()-1);continue;}iter++;}*/}/*Input: user_inputDetermine if user_input indicate exiting the shellIf so, return true, otherwise return false */bool exit_shell_cmd(const vector<string> & handled_user_input){if (handled_user_input.size()==1 && handled_user_input[0]=="exit")return true;elsereturn false;}/*Execute the command using tokenized commandReturn command execution's return value.*/int execute_command_core(const vector<string> & tokenized_command){if (tokenized_command.size()==0)return 0;//empty command treated as successint pid;int status;pid = fork();if (pid==0) //meaning it is the child process{vector<string> clean_command;//saves commands which erase the I/O redirection partshandle_io_redirection(tokenized_command,clean_command);vector<char *> execvp_argument;generate_argument(clean_command,execvp_argument);execvp(clean_command[0].c_str(),&execvp_argument[0]);cerr<<"Command not found!"<<endl;//should not execute this line unless execvp failsexit(EXIT_FAILURE);}pid = wait(&status);//the parent process wait the child process to finish and fetch the exit_statusreturn status;}/*Check command_exit_status and report according to the command_exit_status if encounter with failureReference to http://tldp.org/LDP/abs/html/exitcodes.html*/void check_and_report_failure(int command_exit_status){if (command_exit_status<0 || command_exit_status>255){//cerr<<"Exit status out of range [0,255]: "<<command_exit_status<<endl;//should never happen. In practice, it happens a lot...return;}if (command_exit_status>128 && command_exit_status!=130){cerr<<"Fatal error signal: "<<(command_exit_status-128)<<endl;return;}switch (command_exit_status){case 0:{break;//succeed, display nothing}case 1:{cerr<<"Catchall for general errors"<<endl;break;}case 2:{cerr<<"Misuse of shell builtins (according to Bash documentation)"<<endl;break;}case 126:{cerr<<"Command invoked cannot execute"<<endl;break;}case 127:{cerr<<"Command not found!"<<endl;break;}case 128:{cerr<<"Invalid argument to exit!"<<endl;break;}case 130:{cerr<<"Script terminated by Control-C"<<endl;break;}default:{ cerr<<"Fail to execute command where command_exit_status is "<<command_exit_status<<endl;}}}/*If tokenized_user_input is a built-in command, handle it and return trueOtherwise return false*/bool handle_built_in_command(const vector<string> & tokenized_user_input){int size = (int)tokenized_user_input.size();/*Handle empty command*/if (size<1)return true;/*Handle built-in command*/if (tokenized_user_input[0]=="cd"){if (size==1)//will add support to "cd" command in next iteration{cout<<"cd should let you go home.Not supported in this iteration yet. We are sorry. >_<"<<endl;return true;}else{if (chdir(tokenized_user_input[1].c_str())!=0)//change current directory fails will return -1{cout<<"cd: "<<tokenized_user_input[1]<<": No such file or directory"<<endl;}return true;}}else if (tokenized_user_input[0]=="echo"){if (size==1){cout<<endl;//imitate what shell do}if (size>=2){cout<<tokenized_user_input[1];for (int i=2;i<size;i++){cout<<" "<<tokenized_user_input[i];}cout<<endl;}return true;}return false;}//=========================================/*Function library*///=========================================/*split the str with delimiter, return the result in vector<string>*/vector<string> split_string(const string& str,const string& delimiter){vector<string> result;string::size_type pos1 = str.find_first_not_of(delimiter,0);string::size_type pos2 = str.find_first_of(delimiter,pos1);while (!(pos1==string::npos && pos2==string::npos))//{result.push_back(str.substr(pos1,pos2-pos1));pos1 = str.find_first_not_of(delimiter,pos2);pos2 = str.find_first_of(delimiter,pos1);}return result;}/*Generate the argument command char* array used by system call execvp, which must end with NULL element*/void generate_argument(const vector<string> & tokenized_command,vector<char *> & execvp_argument){execvp_argument.clear();int size = (int)tokenized_command.size();//to avoid warningfor (int i=0;i<size;i++){execvp_argument.push_back(const_cast<char *>(tokenized_command[i].c_str()));}execvp_argument.push_back(NULL);}/*Two tasks for this function:1. Handle the I/O redirection using dup2 to manipulate the file descriptors2. Generate a clean_command to be executed which erase the i/O redirection tokensPS:Why we don't make change on tokenized_command?Because we think the execute_command's meaning should not change it, if commands changed after execution it will get people confused.And the cost is anthor vector<string> which I know can be saved.*/void handle_io_redirection(const vector<string> & tokenized_command,vector<string> & clean_command){clean_command.clear();int size = (int)tokenized_command.size();for (int i=0;i<size;i++){if (!handle_input_redirection(tokenized_command[i]) && !handle_output_redirection(tokenized_command[i])){clean_command.push_back(tokenized_command[i]);}}}/*These two functions do the similar things:1. Determine if the token is an I/O redirection token2. If it is, manipulate the file descriptor accordingly and return true, otherwise return false*/bool handle_input_redirection(const string & token){if (token.length()==0)return false;if (token[0]=='<'){/*We will treat the rest of the token string as file path*/string file_path = token.substr(1);int fd_in = open(file_path.c_str(),O_CREAT | O_RDWR, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);if (fd_in > 0){/*redirect STDOUT for this process*/dup2(fd_in,0);}return true;}else{return false;}}bool handle_output_redirection(const string & token){if (token.length()==0)return false;if (token[0]=='>'){/*We will treat the rest of the token string as file path*/string file_path = token.substr(1);int fd_out = open(file_path.c_str(),O_CREAT | O_RDWR, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);if (fd_out > 0){/*redirect STDOUT for this process*/dup2(fd_out,1);}return true;}else{return false;}}//=========================================


0 0