shell
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>
#include <sys/types.h>
#define MAX_CMDLINE_LENGTH 1024 /* max cmdline length in a line*/
#define MAX_BUF_SIZE 4096 /* max buffer size */
#define MAX_CMD_ARG_NUM 32 /* max number of single command args */
#define WRITE_END 1 // pipe write end
#define READ_END 0 // pipe read end
/*
* 需要大家完成的代码已经用注释`TODO:`标记
* 可以编辑器搜索找到
* 使用支持TODO高亮编辑器(如vscode装TODO highlight插件)的同学可以轻松找到要添加内容的地方。
*/
/*
int split_string(char* string, char *sep, char** string_clips);
function: split string by sep and delete whitespace at clips' head & tail
arguments: char* string, Input, string to be splitted
char* sep, Input, the symbol used to split string
char** string_clips, Input/Output, giving an allocated char* array
and return the splitted string array
return value: int, number of splitted strings
*/
int split_string(char* string, char *sep, char** string_clips) {
char string_dup[MAX_BUF_SIZE];
string_clips[0] = strtok(string, sep);
int clip_num=0;
do {
char *head, *tail;
head = string_clips[clip_num];
tail = head + strlen(string_clips[clip_num]) - 1;
while(*head == ' ' && head != tail)
head ++;
while(*tail == ' ' && tail != head)
tail --;
*(tail + 1) = '\0';
string_clips[clip_num] = head;
clip_num ++;
}while(string_clips[clip_num]=strtok(NULL, sep));
return clip_num;
}
/*
执行内置命令
arguments:
argc: 命令的参数个数
argv: 依次代表每个参数,注意第一个参数就是要执行的命令,
若执行"ls a b c"命令,则argc=4, argv={"ls", "a", "b", "c"}
return:
int, 若执行成功返回0,否则返回值非零
*/
int exec_builtin(int argc, char**argv) {
if(argc == 0) {
return 0;
}
/* TODO: 添加和实现内置指令 */
if (strcmp(argv[0], "cd") == 0) {
chdir(argv[1]);
return 0;
} else if (strcmp(argv[0], "pwd") == 0) {
char buf[255];
getcwd(buf,255);
printf("%s\n",buf);
return 0;
} else if (strcmp(argv[0], "exit") == 0){
exit(0);
} else {
// 不是内置指令时
return -1;
}
}
/*
在本进程中执行,且执行完毕后结束进程。
arguments:
argc: 命令的参数个数
argv: 依次代表每个参数,注意第一个参数就是要执行的命令,
若执行"ls a b c"命令,则argc=4, argv={"ls", "a", "b", "c"}
return:
int, 若执行成功则不会返回(进程直接结束),否则返回非零
*/
int execute(int argc, char** argv) {
if(exec_builtin(argc, argv) == 0) {
exit(0);
}
/* TODO:运行命令 */
return execvp(argv[0],argv);
}
int main() {
/* 输入的命令行 */
char cmdline[MAX_CMDLINE_LENGTH];
int i,j,k;
/* 由管道操作符'|'分割的命令行各个部分,每个部分是一条命令 */
char *commands[128];
int cmd_count;
while (1) {
/* TODO:增加打印当前目录,格式类似"shell:/home/oslab ->",你需要改下面的printf */
char cwd[255];
getcwd(cwd,255);
printf("shell:%s -> ",cwd);
fflush(stdout);
fgets(cmdline, 256, stdin);
strtok(cmdline, "\n");
/* 拆解命令行 */
cmd_count = split_string(cmdline, "|", commands);
if(cmd_count == 0) {
continue;
} else if(cmd_count == 1) {// 没有管道的单一命令
char *argv[MAX_CMD_ARG_NUM];
int argc=split_string(cmdline," ",argv);// TODO:处理参数,分出命令名和参数
/* 在没有管道时,内建命令直接在主进程中完成,外部命令通过创建子进程完成 */
if(exec_builtin(argc, argv) == 0) {
continue;
}
int pid=fork();// TODO:创建子进程,运行命令,等待命令运行结束
if(pid==0)
execute(argc,argv);
while(wait(NULL)>0);
} else if(cmd_count == 2) { // 两个命令间的管道
int pipefd[2];
int ret = pipe(pipefd);
if(ret < 0) {
printf("pipe error!\n");
continue;
}
// 子进程1
int pid = fork();
if(pid == 0) {
/*TODO:子进程1 将标准输出重定向到管道,注意这里数组的下标被挖空了要补全*/
close(pipefd[0]);
dup2(pipefd[1], STDOUT_FILENO);
close(pipefd[1]);
/*
在使用管道时,为了可以并发运行,所以内建命令也在子进程中运行
因此我们用了一个封装好的execute函数
*/
char *argv[MAX_CMD_ARG_NUM];
int argc = split_string(commands[0], " ", argv);
execute(argc, argv);
exit(255);
}
// 因为在shell的设计中,管道是并发执行的,所以我们不在每个子进程结束后才运行下一个
// 而是直接创建下一个子进程
// 子进程2
pid = fork();
if(pid == 0) {
/* TODO:子进程2 将标准输入重定向到管道,注意这里数组的下标被挖空了要补全 */
close(pipefd[1]);
dup2(pipefd[0], STDIN_FILENO);
close(pipefd[0]);
char *argv[MAX_CMD_ARG_NUM];
int argc = split_string(commands[1], " ", argv);
execute(argc, argv);
exit(255);
// TODO:处理参数,分出命令名和参数,并使用execute运行
// 在使用管道时,为了可以并发运行,所以内建命令也在子进程中运行
// 因此我们用了一个封装好的execute函数
}
close(pipefd[WRITE_END]);
close(pipefd[READ_END]);
while (wait(NULL) > 0);
} else { // 三个以上的命令
int read_fd; // 上一个管道的读端口(出口)
for(int i=0; i<cmd_count; i++) {
int pipefd[2];
// TODO:创建管道,n条命令只需要n-1个管道,所以有一次循环中是不用创建管道的
int ret = pipe(pipefd);
if(ret < 0) {
printf("pipe error!\n");
continue;
}
int pid = fork();
if(pid == 0) {
// TODO:除了最后一条命令外,都将标准输出重定向到当前管道入口
if(i!=cmd_count-1){
dup2(pipefd[1], STDOUT_FILENO);
close(pipefd[1]);
}
// TODO:除了第一条命令外,都将标准输出重定向到上一个管道入口
if(i){
dup2(read_fd, STDIN_FILENO);
close(read_fd);
}
char *argv[MAX_CMD_ARG_NUM];
int argc = split_string(commands[i], " ", argv);
execute(argc, argv);
// TODO:处理参数,分出命令名和参数,并使用execute运行
// 在使用管道时,为了可以并发运行,所以内建命令也在子进程中运行
// 因此我们用了一个封装好的execute函数
}
if(i!=cmd_count-1)
read_fd=pipefd[0];
close(pipefd[WRITE_END]);
/* 父进程除了第一条命令,都需要关闭当前命令用完的上一个管道读端口
* 父进程除了最后一条命令,都需要保存当前命令的管道读端口
* 记得关闭父进程没用的管道写端口 */
// 因为在shell的设计中,管道是并发执行的,所以我们不在每个子进程结束后才运行下一个
// 而是直接创建下一个子进程
}
while (wait(NULL) > 0);// TODO:等待所有子进程结束
}
}
}