操作系统实验四:Shell的实现
一、实验内容
实现具有管道、重定向功能的shell
能够执行一些简单的基本命令,如进程执行、列目录等。
二、实验目的
1.学习并理解linux中shell的知识;
2.重点学会编程实现管道和重定向的功能;
3.实现自己的shell
三、设计思路和流程图
1.对输入的命令进行解析
实验内容主要是管道和重定向,这两个功能涉及shell“|”和“<”以及“>”等不同符号,所以要对输入的命令进行解析。初步按照空格来分,之后再按照<、>、|这些涉及管道和重定向的符号来分。
2.简单命令的执行
使用函数execvp可以实现简单的命令,这些命令暂时不涉及管道和重定向,函数原型为int execvp(const char *file ,char * const argv []);,execvp()会从PATH 环境变量所指的目录中查找符合参数file 的文件名,找到后便执行该文件,然后将第二个参数argv传给该欲执行的文件。为了不造成阻塞,这里启用了一个新线程实现它,最后父进程需等待子进程,以回收分配给它的资源。下面有些地方也用到这种方法。
3.输入输出重定向的实现
实现重定向的主要函数是freopen,FILE *freopen( const char *path, const char *mode, FILE *stream );path: 文件名,用于存储输入输出的自定义文件名。 mode: 文件打开的模式。和fopen中的模式(如r-只读, w-写)相同。 stream: 一个文件,通常使用标准流文件。函数实现重定向,把预定义的标准流文件定向到由path指定的文件中。要注意的是第二个参数,刚开始我是用的a+,结果每次输出都加到文件末尾。后来查了一下,改成w+可以先清空再写入文件。
4.管道功能的实现
命令之间通过|符号来分隔,使用pipe函数来建立管道。如何分隔这些命令呢?上面是写一个函数通过空格来分离每个字符串,这里通过strtok_r函数来分隔命令。利用pipe函数生成的的读取端和写入端,第一条命令的输出作为第二条命令的输入,从而实现管道的功能。
四、主要数据结构及说明
主要使用了数组和指针,存放相关的命令,通过字符串操作实现一些基本的逻辑。
五、源程序并附上注释
上面所说的设计思路与程序中的函数是对应的。
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <string.h> #include <sys/stat.h> #include<signal.h> #include <fcntl.h> #define hist_size 1024 char *hist[hist_size]; int f = 0; //to save change in directory int head = 0, filled = 0; //1-parse user's input void parse(char *word, char **argv) { int count = 0; memset(argv, 0, sizeof(char*)*(64));//copies 0 to the first 64 characters of the string pointed to by the argument argv. char *lefts = NULL; const char *split = " "; //setting delimeter while (1) { char *p = strtok_r(word, split, &lefts);//ref-"strtok-r":http://baike.baidu.com/view/6991509.htm?fr=aladdin if (p == NULL) { break; } argv[count] = p;//argv is an array, it stores each value of your input divided by " " word = lefts; //printf("%s\n",argv[count]); //printf("%s\n",word); count++; } if (strcmp(argv[0], "exit") == 0) exit(0); else if (strcmp(argv[0], "cd") == 0) { int ch = chdir(argv[1]); //ref-"chdir":http://baike.baidu.com/view/653970.htm?fr=aladdin /*if(ch<0) { printf("No such file or directory %d\n.",ch); }*/ f = 1; } } //2-get the first word char *trim(char *string)//remove extra spaces { int i = 0; int j = 0; char *ptr = malloc(sizeof(char*)*strlen(string)); for (i = 0; string[i] != '\0'; i++) if (string[i] != ' ') { ptr[j] = string[i]; j++; } ptr[j] = '\0'; string = ptr; //printf("%s\n",string); //printf("%d\n",j); return string; } //3-execute the basic order void execute(char **argv) { pid_t pid; int status; //fork child process if ((pid = fork()) < 0) { printf("error:fork failed.\n"); exit(1); } else if (pid == 0) { if (execvp(argv[0], argv) < 0 && strcmp(argv[0], "cd"))//ref-"execvp":http://baike.baidu.com/view/1745420.htm?fr=aladdin printf("error:invalid command.\n"); exit(0); } else { while (wait(&status) != pid)//ref-"wait":http://see.xidian.edu.cn/cpp/html/289.html ; } } //4-output redirect //output:file in which we need to print the output void execute_file(char **argv, char *output) { //printf("output:%s\n",output); //printf("argv:%s\n",*argv); pid_t pid; int status, flag; char *file = NULL; if ((pid = fork()) < 0) { printf("error:fork failed.\n"); exit(1);//fclose(fd1); } else if (pid == 0) { if (strstr(output, ">")>0)//returns a pointer to first occurence after > or null { char *p = strtok_r(output, ">", &file); output += 1; //change2 file = trim(file);//get the first word of file, that is to say, get the first word after > flag = 1; //printf("file : %s output : %s \n",*argv,output); int old_stdout = dup(1); //printf("mark\n"); //output redirect FILE *fp1 = freopen(output, "w+", stdout);//ref-"freopen":http://baike.baidu.com/view/656692.htm?fr=aladdin //printf("mark\n"); execute_file(argv, file); fclose(stdout); FILE *fp2 = fdopen(old_stdout, "w");//ref-"fdopen":http://baike.baidu.com/view/656646.htm?fr=aladdin *stdout = *fp2; exit(0); } if (strstr(output, "<") > 0) { char *p = strtok_r(output, "<", &file); file = trim(file); flag = 1; int fd = open(file, O_RDONLY);//ref-"open":http://see.xidian.edu.cn/cpp/html/238.html if (fd<0) { printf("No such file or directory."); exit(0); } } if (strstr(output, "|")>0) { fflush(stdout); printf("here"); fflush(stdout); char *p = strtok_r(output, "|", &file); file = trim(file); flag = 1; //fflush(stdout);printf("%s",file);fflush(stdout); char *args[64]; parse(file, args); execute(args); } int old_stdout = dup(1); FILE *fp1 = freopen(output, "w+", stdout); if (execvp(argv[0], argv) < 0) printf("error:in exec"); fclose(stdout); FILE *fp2 = fdopen(old_stdout, "w"); *stdout = *fp2; exit(0); } else { while (wait(&status) != pid) ; } } //5-input redirect void execute_input(char **argv, char *output) { pid_t pid; int fd; char *file; int flag = 0; int status; if ((pid = fork()) < 0) { printf("error:fork failed\n"); exit(1); } else if (pid == 0) { if (strstr(output, "<")>0) { char *p = strtok_r(output, "<", &file); file = trim(file); flag = 1; //printf("file : %s output : %s \n",file,output); fd = open(output, O_RDONLY); if (fd<0) { printf("No such file or directory."); exit(0); } output = file; } if (strstr(output, ">")>0) { char *p = strtok_r(output, ">", &file); file = trim(file); flag = 1; fflush(stdout); //printf("file : %s output : %s \n",file,output); fflush(stdout); int old_stdout = dup(1); FILE *fp1 = freopen(file, "w+", stdout); execute_input(argv, output); fclose(stdout); FILE *fp2 = fdopen(old_stdout, "w"); *stdout = *fp2; exit(0); } if (strstr(output, "|") > 0) { char *p = strtok_r(output, "|", &file); file = trim(file); flag = 1; char *args[64]; parse(file, args); int pfds[2]; pid_t pid, pid2; int status, status2; pipe(pfds); int fl = 0; if ((pid = fork()) < 0) { printf("error:fork failed\n"); exit(1); } if ((pid2 = fork()) < 0) { printf("error:fork failed\n"); exit(1); } if (pid == 0 && pid2 != 0) { close(1); dup(pfds[1]); close(pfds[0]); close(pfds[1]); fd = open(output, O_RDONLY); close(0); dup(fd); if (execvp(argv[0], argv) < 0) { close(pfds[0]); close(pfds[1]); printf("error:in exec"); fl = 1; exit(0); } close(fd); exit(0); } else if (pid2 == 0 && pid != 0 && fl != 1) { close(0); dup(pfds[0]); close(pfds[1]); close(pfds[0]); if (execvp(args[0], args) < 0) { close(pfds[0]); close(pfds[1]); printf("error:in exec"); exit(0); } } else { close(pfds[0]); close(pfds[1]); while (wait(&status) != pid); while (wait(&status2) != pid2); } exit(0); } fd = open(output, O_RDONLY); close(0); dup(fd); if (execvp(argv[0], argv) < 0) { printf("error:in exec"); } close(fd); exit(0); } else { while (wait(&status) != pid); } } //6-implement pipe void execute_pipe(char **argv, char *output) { int pfds[2], pf[2], flag; char *file; pid_t pid, pid2, pid3; int status, status2, old_stdout; pipe(pfds);//create pipe //pfds[0]:read pfds[1]:write int blah = 0; char *args[64]; char *argp[64]; int fl = 0; if ((pid = fork()) < 0) { printf("error:fork failed\n"); exit(1); } if ((pid2 = fork()) < 0) { printf("error:fork failed\n"); exit(1); } if (pid == 0 && pid2 != 0) { close(1); dup(pfds[1]); close(pfds[0]); close(pfds[1]); if (execvp(argv[0], argv) < 0)//run the command { close(pfds[0]); close(pfds[1]); printf("error:in exec"); fl = 1; kill(pid2, SIGUSR1); exit(0); } } else if (pid2 == 0 && pid != 0) { if (fl == 1){ exit(0); } if (strstr(output, "<") > 0) { char *p = strtok_r(output, "<", &file); file = trim(file); flag = 1; parse(output, args);//divide output to the array args execute_input(args, file); close(pfds[0]); close(pfds[1]); exit(0); } if (strstr(output, ">") > 0) { char *p = strtok_r(output, ">", &file); file = trim(file); flag = 1; //fflush(stdout);printf("file : %s output : %s \n",file,output);fflush(stdout); parse(output, args); blah = 1; } else { parse(output, args); } close(0); dup(pfds[0]); close(pfds[1]); close(pfds[0]); if (blah == 1) { old_stdout = dup(1); FILE *fp1 = freopen(file, "w+", stdout); } if (execvp(args[0], args) < 0) { fflush(stdout); printf("error:in exec %d", pid); kill(pid, SIGUSR1); close(pfds[0]); close(pfds[1]); } fflush(stdout); printf("HERE"); //kill (pid, SIGUSR1); if (blah == 1) { fclose(stdout); FILE *fp2 = fdopen(old_stdout, "w"); *stdout = *fp2; } } else { close(pfds[0]); close(pfds[1]); while (wait(&status) != pid); while (wait(&status2) != pid2); } } //7-implement pipe void execute_pipe2(char **argv, char **args, char **argp) { int status; int i; int pipes[4]; pipe(pipes); pipe(pipes + 2); if (fork() == 0) { dup2(pipes[1], 1); close(pipes[0]); close(pipes[1]); close(pipes[2]); close(pipes[3]); if (execvp(argv[0], argv) < 0) { fflush(stdout); printf("error:in exec"); fflush(stdout); close(pipes[0]); close(pipes[1]); close(pipes[2]); close(pipes[3]); exit(1); } } else { if (fork() == 0) { dup2(pipes[0], 0); dup2(pipes[3], 1); close(pipes[0]); close(pipes[1]); close(pipes[2]); close(pipes[3]); if (execvp(args[0], args) < 0) { fflush(stdout); printf("error:in exec"); fflush(stdout); close(pipes[0]); close(pipes[1]); close(pipes[2]); close(pipes[3]); exit(1); } } else { if (fork() == 0) { dup2(pipes[2], 0); close(pipes[0]); close(pipes[1]); close(pipes[2]); close(pipes[3]); if (execvp(argp[0], argp) < 0) { fflush(stdout); printf("error:in exec"); fflush(stdout); close(pipes[0]); close(pipes[1]); close(pipes[2]); close(pipes[3]); exit(1); } } } } close(pipes[0]); close(pipes[1]); close(pipes[2]); close(pipes[3]); for (i = 0; i < 3; i++) wait(&status); } /* int main() { char word[1024]="ls>a.txt"; char *argv[1024]; char *file=NULL; char *p=strtok_r(word ,">", &file); file=trim(file); parse(word,argv); execute_file(argv,file); }*/ int main() { char line[1024]; char *argv[64]; char *args[64]; char *left; size_t size = 0; char ch; int count = 0; char *tri; char *second; char *file; int i; for (i = 0; i < hist_size; i++) { hist[i] = (char *)malloc(150); } while (1) { count = 0; int flag = 0; char *word = NULL; char *dire[] = { "pwd" }; fflush(stdout); printf("SHELL~"); fflush(stdout); execute(dire); //print the current directory, we can also use getcwd() printf("$"); int len = getline(&word, &size, stdin); if (*word == '\n') continue; word[len - 1] = '\0'; char *file = NULL; int i = 0; char *temp = (char *)malloc(150); strcpy(temp, word); parse(temp, argv); strcpy(hist[(head + 1) % hist_size], word); //storing an entry in history head = (head + 1) % hist_size; filled = filled + 1; for (i = 0; word[i] != '\0'; i++) { if (word[i] == '>') { //printf("%s\n",word); //has the initial command char *p = strtok_r(word, ">", &file); file = trim(file); //printf("%s\n",file); flag = 1; break; } else if (word[i] == '<') { char *p = strtok_r(word, "<", &file); file = trim(file); //printf("file : %s",file); flag = 2; break; } else if (word[i] == '|') { char *p = strtok_r(word, "|", &left); flag = 3; break; } } if (strcmp(word, "exit") == 0) { exit(0); } if (flag == 1) { parse(word, argv); //parsed command stored in argv //printf("At the right place"); execute_file(argv, file); } else if (flag == 2) { parse(word, argv); execute_input(argv, file); } else if (flag == 3) { char *argp[64]; char *output, *file; if (strstr(left, "|") > 0) { char *p = strtok_r(left, "|", &file); parse(word, argv); parse(left, args); parse(file, argp); execute_pipe2(argv, args, argp); } else { parse(word, argv); execute_pipe(argv, left); } } else { parse(word, argv); execute(argv); } } }
六、程序运行结果及分析
如图,为了便于观察,我用红色框框对命令和结果做了分隔。
可以看出,基本的管道和重定向功能已经实现。
七、实验体会
shell的实现是四个实验中需要写代码最多的一个,因为之前对这个部分的编程知识了解甚少,所以我找了很多资料学习,尤其是其中很多函数的用法,比如pipe、execvp等等有所了解。做完实验最大的感触就是很多书讲得都太笼统,只讲了一些概念和原理,但自己真正要动手实现起来,是有一定难度的。