简单地实现 Shell:MSH——输入输出重定向、管道

该任务将通过实现一个小的shell(我们称为143A shell)来教您如何使用Unix系统调用接口和shell。

您可以在任何支持Unix API的操作系统(Linux Openlab计算机,运行Linux或Linux VM的笔记本电脑,甚至MacOS等)上进行此分配。

我们将在以下部分中构建shell:1)读取和解析命令,2)执行程序,3)实现对I / O重定向的支持,以及4)实现对管道的支持。

正如我们将看到的,shell是一个实质上等待用户输入,执行命令并重复的程序。我们将通过仅调用一个函数sh_loop来无限期地循环,读取,解释和执行命令,从而使外壳保持简单。通常,shell会做更多的事情(与初始化,配置,终止,关闭等有关的步骤)。如果将以上代码片段放入文件sh.c中,则可以使用C编译器(例如gcc)对其进行编译。

Shell的主要工作是执行命令。一种分解方法是:

  1. 从标准输入读取命令。
  2. 通过将命令字符串分成程序字符串和参数字符串来解析命令字符串。
  3. 执行程序,并将适当的参数传递给它。

它在循环中运行,每次循环执行时都会向用户提供提示:#

#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>

int pid=-1;	//当前的子进程
int exeNum=0;	//当前等待执行的程序数目
char tmp[100];	//输入的字符串

typedef struct 	//存储一条指令
{
	char name[100];	//目标文件
	char infile[100];	//重定向输入文件名
	char outfile[100];	//重定向输出文件名
	char args[20][100];	//输入变量
	int ein;	//是否应该从文件重定向输入
	int eout;	//是否应该从文件重定向输入
	int epipe;	//是否重定向输出到管道
	int ipipe;	//是否从管道重定向输入
	int argNum;	//变量数目
} node;

void initNode(node *n){	//初始化结点
	n->ein=0;n->eout=0;n->ipipe=0;
	n->epipe=0;n->argNum=0;
}

typedef void (*sighandler_t) (int);	//中断捕捉
void sigcat() {
	if(pid >0 ) kill(pid, SIGKILL);//杀死子进程
}


int main(int argc,char *argv[]) {
	signal(SIGINT, (sighandler_t)sigcat);	//注册一个本进程处理中断信号的函数
	printf("# ");
	while(1){
		node exe[20];	//初始化
		exeNum=0;pid=-1;
		int readName=1;
		int echoHello=1;
		//获取命令阶段
		while(1) {	//循环读取命令到数组中
			if(scanf("%s", tmp)==EOF){	//输入结束,退出
				return 0;
			}
			if(echoHello){	//输出提示符
				printf("# ");
				echoHello=0;
			}

			if (readName) {	//首次读取
				initNode(&exe[exeNum]);
				strcpy(exe[exeNum].name, tmp);	//读取执行文件名
				strcpy(exe[exeNum].args[0], tmp);	//复制存储参数
				readName = 0;
				if(exeNum>0){	//发生了管道
					exe[exeNum].ipipe=1;
				}
			}
			else if (strcmp(tmp, ">")==0) {	//重定向输出
				scanf("%s", exe[exeNum].outfile);
				exe[exeNum].eout=1;
			}
			else if (strcmp(tmp, "<")==0) {	//重定向输入
				scanf("%s", exe[exeNum].infile);
				exe[exeNum].ein=1;
			}
			else if (strcmp(tmp, "|")==0) {	//遇到管道
				exe[exeNum].epipe=1;
				++exeNum; readName=1;
			}
			else {
				strcpy(exe[exeNum].args[++(exe[exeNum].argNum)], tmp);	//参数的存入
			}

			char ch;
			ch = getchar();
			if(ch=='\n') break;
		} 


		//命令解析执行阶段
		int pin[2];
		int pout[2]	;
		pipe(pin);
		pipe(pout);
		for(int id=0;id<=exeNum;id++){

			pid=fork();
			if(pid==0){

				char* argList[20]={0};	//构建参数列表
				for(int i=0;i<=exe[id].argNum;i++){
					argList[i]=&exe[id].args[i][0];
				}

				// stdin、stdout、stderr ⽂件描述符分别为 0	、1、2
				if(exe[id].ein){	//从文件重定向输入
					int fd = open(exe[id].infile,O_RDONLY);
					dup2(fd,0);
				}

				if(exe[id].eout){	//从文件重定向输出
					int fd = open(exe[id].outfile,O_CREAT|O_WRONLY|O_TRUNC,0664);
					dup2(fd,1);
				}

				if(exe[id].ipipe){		//从管道重定向输入
					close(pin[1]);	//关闭输入管道的输入端
					dup2(pin[0],0);	//替换掉标准输入
					close(pin[0]);	//关闭输入管道的输出端
				}

				if(exe[id].epipe){	//从管道重定向输出
					dup2(pout[1],1);	//替换掉标准输出
					close(pout[1]);	//关闭输出管道的输出端
				}

 				execvp(exe[id].name, argList);//execvp() 会从 PATH 环境变量所指的⽬录中查找符合参数 file 的⽂件名,找到后便执⾏该⽂件,然后将第⼆个参数 argv 传给该欲执⾏的⽂件。如果执⾏成功则函数不会返回,执⾏失败则直接返回 -1,失败原因存于 errno 中。

 				perror("Fail to execute: ");
 				exit(EXIT_FAILURE);
			}
			else{	//父进程
				close(pin[1]);
				wait(NULL);
				close(pout[1]);
				pipe(pin);
				char ch;
				while(read(pout[0],&ch,1)){
					write(pin[1],&ch,1);
				}
				pipe(pout);

			}

		}


	}

}

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注

Back to Top