实现shell程序,要求在第2版的基础上,添加如下功能
-
实现管道
-
只要求连接两个命令,不要求连接多个命令
-
不要求同时处理管道和重定向
# 执行sh3 $ ./sh3 # 执行命令cat和wc,使用管道连接cat和wc > cat /etc/passwd | wc -l
-
考虑如何实现管道和文件重定向,暂不做强制要求
$ cat input.txt 3 2 1 3 2 1 $ cat
output.txt $ cat output.txt 1 2 3
本题实现参考sh3的实现提示文档 https://www.linuxmooc.com/teach/os/sh3-hint.pdf 。首先定义command数据结构:
struct command {
int argc;
char *argv[MAX_ARGC];
char *input; //用于重定向输入
char *output; //用于重定向输出
};
并定义全局变量:
int command_count; struct command commands[MAX_COMMANDS];
command_count用于对命令计数,若值为1则该命令为非管道命令,值>1则为管道命令。实现处理字符串函数分割用户键入的完整命令并存储在结构体数组commands中,在函数void parse_commands(char *line)中用 '|' 作为分隔符切割 line, 将多条命令按 | 分割,该函数调用了子函数void parse_command(char *line, int index),用 ' ' 作为分隔符切割 line, 将一条命令按空格分割。并且实现void commands_dump()函数用于验证字符串处理是否正确,经过测试发现不能使用strtok,而应该使用strtok_r,否则存储结果的结构体数组只有第一条命令。
接下来需要重构mysys函数,调用函数进行字符串处理之后,需要对command_count的值做判断,若为0直接返回;若为1 表示非管道命令,首先判断是否为内置命令,若是调用相应的函数完成处理后返回;否则创建进程,调用子函数exec_simple()装入程序执行;若为2表示管道命令,创建进程,调用子函数exec_pipe()执行管道命令,在函数中先创建管道, 再创建进程,子进程将标准输出定向到管道的写端,父进程将标准输入定向到管道的读端。
3.代码#include4.运行结果#include #include #include #include #include #include #include #define SIZE 256 #define MAX_ARGC 16 #define MAX_COMMANDS 16 #define MAX_SIZE 256 struct command { int argc; char *argv[MAX_ARGC]; char *input; //用于重定向输入 char *output; //用于重定向输出 }; int command_count; struct command commands[MAX_COMMANDS]; void panic(char *message) { perror(message); exit(EXIT_FAILURE); } // 用 ' ' 作为分隔符切割 line, 将一条命令按空格分割 void parse_command(char *line, int index) { char *field = malloc(sizeof(line)); strcpy(field, line); char *delim = " n"; commands[index].argc = 0; char *ptr = NULL; for(int i = 0; i < MAX_ARGC; i++) commands[index].argv[i] = NULL; commands[index].input = NULL; commands[index].output = NULL; char *word = strtok_r(field, delim, &ptr); while(word) { if(strchr(word, '>')) commands[index].output = word + 1; else if(strchr(word, '<')) commands[index].input = word + 1; else commands[index].argv[commands[index].argc++] = word; word = strtok_r(NULL, delim, &ptr); } } // 用 '|' 作为分隔符切割 line, 将多条命令按 | 分割 void parse_commands(char *line) { // char *fields = malloc(sizeof(line)); char *fields = (char *)malloc(MAX_SIZE); strcpy(fields, line); char *delim = "|n"; char *ptr = NULL; command_count = 0; char *field = strtok_r(fields, delim, &ptr); while(field) { parse_command(field, command_count); command_count++; field = strtok_r(NULL, delim, &ptr); } } // 打印 command 的 argc 和 argv, 验证字符串处理是否正确 void command_dump(struct command command) { printf(" argc = %dn", command.argc); printf(" argv = {"); for(int i = 0; i < command.argc; i++) { printf(""%s"", command.argv[i]); if(i < command.argc - 1) printf(", "); } printf("}n"); } // 打印 commands 中的每一个 command, 验证字符串处理是否正确 void commands_dump() { printf("n-------------------parse commands start-------------------n"); for(int i = 0; i < command_count; i++) { printf("ncommand[%d]n", i); command_dump(commands[i]); } printf("n-------------------parse commands end---------------------nn"); } // 处理内置命令cd、pwd、exit void handle_built_in_command(int index) { if(strcmp(commands[index].argv[0], "cd") == 0) { int error = chdir(commands[index].argv[1]); if(error < 0) perror("cd"); else { char *path = getcwd(NULL, 0); printf("current working directory: %sn", path); free(path); } } else if(strcmp(commands[index].argv[0], "pwd") == 0) { char *path = getcwd(NULL, 0); printf("current working directory: %sn", path); free(path); } else if(strcmp(commands[index].argv[0], "exit") == 0) { exit(0); } } // 处理文件重定向 void handle_redirect(int index) { if(commands[index].output) { int fd = open(commands[index].output, O_CREAT | O_RDWR); if(fd < 0) panic("open"); dup2(fd, 1); close(fd); } if(commands[index].input) { int fd = open(commands[index].input, O_CREAT | O_RDWR); if(fd < 0) panic("open"); dup2(fd, 0); close(fd); } } // 执行非管道命令 void exec_simple(int index) { // 处理需要文件重定向的情况 if(commands[index].input || commands[index].output) { handle_redirect(index); } // 装入程序执行 int error = execvp(commands[index].argv[0], commands[index].argv); if(error < 0) perror("execvp"); } // 执行管道命令 void exec_pipe() { int fd[2]; int pid; pipe(fd); // 先创建管道, 再创建进程 pid = fork(); if(pid == 0) { dup2(fd[1], 1); // 标准输出定向到管道的写端 close(fd[0]); close(fd[1]); exec_simple(0); } wait(NULL); dup2(fd[0], 0); // 标准输入定向到管道的读端 close(fd[0]); close(fd[1]); exec_simple(1); } void mysys(char *command) { parse_commands(command); // commands_dump(); if(command_count == 0) return ; else if(command_count == 1) { // 单独处理内置命令cd、pwd、exit if(strcmp(commands[0].argv[0], "cd") == 0 || strcmp(commands[0].argv[0], "pwd") == 0 || strcmp(commands[0].argv[0], "exit") == 0) { handle_built_in_command(0); return ; } pid_t pid = fork(); if(pid == 0) exec_simple(0); wait(NULL); } else if(command_count == 2) { pid_t pid = fork(); if(pid == 0) exec_pipe(); wait(NULL); } } int main() { char command[SIZE]; int count; while(1) { write(1, ">", sizeof(">")); count = read(0, command, sizeof(command)); command[count] = 0; mysys(command); } return 0; }
保留commands_dump()函数验证字符串处理是否正确:
$ gcc sh3.c -o sh3
$ ./sh3
>cat /etc/passwd | wc -l
-------------------parse commands start-------------------
command[0]
argc = 2
argv = {"cat", "/etc/passwd"}
command[1]
argc = 2
argv = {"wc", "-l"}
-------------------parse commands end---------------------
43
>exit
-------------------parse commands start-------------------
command[0]
argc = 1
argv = {"exit"}
-------------------parse commands end---------------------
注释commands_dump()之后:
$ gcc sh3.c -o sh3 $ ./sh3 >cat /etc/passwd | wc -l 43 >exit



