Mini-Shell with pipeline

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 command
			continue;
		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 command
				continue;
		
			/*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 command
			for (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 command
					continue;
		
				/*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 parts
					handle_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 way
		
					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 fails
					exit(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_input
Determine if user_input indicate exiting the shell
If so, return true, otherwise return false 
*/
bool exit_shell_cmd(const vector<string> & handled_user_input);

/*
Execute the command using tokenized command
Return 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 failure
Reference 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 true
Otherwise 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 descriptors
2. 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 token
2. 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_input
Determine if user_input indicate exiting the shell
If 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;
	else
		return false;
}

/*
Execute the command using tokenized command
Return 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 success
	
	int 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 parts
		handle_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 fails
		exit(EXIT_FAILURE);
	}
	pid = wait(&status);//the parent process wait the child process to finish and fetch the exit_status
	return status;
}

/*
Check command_exit_status and report according to the command_exit_status if encounter with failure
Reference 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 true
Otherwise 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 warning
	for (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 descriptors
2. Generate a clean_command to be executed which erase the i/O redirection tokens
PS:
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 token
2. 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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值