1.题目要求
实现shell程序,要求在第2版的基础上,添加如下功能
-
实现管道
-
只要求连接两个命令,不要求连接多个命令
-
不要求同时处理管道和重定向
# 执行sh3 $ ./sh3 # 执行命令cat和wc,使用管道连接cat和wc > cat /etc/passwd | wc -l
-
考虑如何实现管道和文件重定向,暂不做强制要求
$ cat input.txt 3 2 1 3 2 1 $ cat <input.txt | sort | uniq | cat >output.txt $ cat output.txt 1 2 3
2.解决思路
本题实现参考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.代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>
#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 = %d\n", 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---------------------\n\n");
}
// 处理内置命令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: %s\n", path);
free(path);
}
}
else if(strcmp(commands[index].argv[0], "pwd") == 0) {
char *path = getcwd(NULL, 0);
printf("current working directory: %s\n", 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;
}
4.运行结果
保留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