#include #include #include #include #include #include #include #include #define MAX_CMD 1024 #define MAX_ARGS 64 #define MAX_PIPES 32 char *trim(char *s) { while (isspace((unsigned char)*s)) s++; if (*s == '\0') return s; char *end = s + strlen(s) - 1; while (end > s && isspace((unsigned char)*end)) end--; *(end + 1) = '\0'; return s; } int parse_args(char *cmd, char **args) { int argc = 0; char *token = strtok(cmd, " \t"); while (token != NULL && argc < MAX_ARGS - 1) { args[argc++] = token; token = strtok(NULL, " \t"); } args[argc] = NULL; return argc; } int split_pipeline(char *buf, char *segments[], int max_segs) { int nseg = 0; char *seg_start = buf; /* 当前段的起始位置 */ char *p = buf; while (*p != '\0') { if (*p == '|') { /* 找到管道符:截断当前段,保存去空格后的指针 */ *p = '\0'; char *seg = trim(seg_start); if (seg[0] != '\0') { /* 跳过空段 */ if (nseg >= max_segs) { fprintf(stderr, "shell: too many pipes (max %d)\n", max_segs); return -1; } segments[nseg++] = seg; } seg_start = p + 1; /* 下一段从 '|' 后面开始 */ } p++; } /* 保存最后一段('|' 后面,或整个命令没有 '|' 时) */ char *last = trim(seg_start); if (last[0] != '\0') { segments[nseg++] = last; } return nseg; } static void exec_cmd_in_child(char **args, int argc, int in_fd, int out_fd) { char *infile = NULL; char *outfile = NULL; char *clean_args[MAX_ARGS]; int clean_argc = 0; for (int i = 0; i < argc; i++) { if (strcmp(args[i], "<") == 0 && i + 1 < argc) { infile = args[++i]; /* 记录输入文件,跳过文件名 */ } else if (strcmp(args[i], ">") == 0 && i + 1 < argc) { outfile = args[++i]; /* 记录输出文件,跳过文件名 */ } else { clean_args[clean_argc++] = args[i]; /* 普通参数保留 */ } } clean_args[clean_argc] = NULL; if (clean_argc == 0) { exit(0); } if (infile) { if (in_fd != STDIN_FILENO) close(in_fd); /* 管道读端不再需要,关掉 */ int fd = open(infile, O_RDONLY); if (fd < 0) { perror("open"); exit(1); } dup2(fd, STDIN_FILENO); close(fd); } else if (in_fd != STDIN_FILENO) { dup2(in_fd, STDIN_FILENO); close(in_fd); } if (outfile) { if (out_fd != STDOUT_FILENO) close(out_fd); int fd = open(outfile, O_WRONLY | O_CREAT | O_TRUNC, 0644); if (fd < 0) { perror("open"); exit(1); } dup2(fd, STDOUT_FILENO); close(fd); } else if (out_fd != STDOUT_FILENO) { dup2(out_fd, STDOUT_FILENO); close(out_fd); } execvp(clean_args[0], clean_args); fprintf(stderr, "shell: command not found: %s\n", clean_args[0]); exit(127); } static void run_single(char *seg) { char buf[MAX_CMD]; strncpy(buf, seg, MAX_CMD - 1); buf[MAX_CMD - 1] = '\0'; char *args[MAX_ARGS]; int argc = parse_args(buf, args); if (argc == 0) return; pid_t pid = fork(); if (pid < 0) { perror("fork"); return; } if (pid == 0) { /* 子进程:从终端 stdin 读,输出到终端 stdout */ exec_cmd_in_child(args, argc, STDIN_FILENO, STDOUT_FILENO); } /* 父进程:等待子进程结束 */ waitpid(pid, NULL, 0); } static void run_pipeline(char *segments[], int nseg) { int npipes = nseg - 1; /* --- 1. 一次性创建所有管道 --- */ int pipes[MAX_PIPES][2]; for (int i = 0; i < npipes; i++) { if (pipe(pipes[i]) < 0) { perror("pipe"); /* 出错:关闭已创建的管道后返回 */ for (int j = 0; j < i; j++) { close(pipes[j][0]); close(pipes[j][1]); } return; } } /* --- 2. 依次 fork 每段命令的子进程 --- */ pid_t pids[MAX_PIPES + 1]; for (int i = 0; i < nseg; i++) { /* 确定本段从哪里读、往哪里写: * 第一段:从终端 stdin 读 * 中间段:从上一个管道的读端读,向下一个管道的写端写 * 最后段:向终端 stdout 写 */ int in_fd = (i == 0) ? STDIN_FILENO : pipes[i - 1][0]; int out_fd = (i == nseg - 1) ? STDOUT_FILENO : pipes[i][1]; /* 解析本段命令的参数 */ char cbuf[MAX_CMD]; strncpy(cbuf, segments[i], MAX_CMD - 1); cbuf[MAX_CMD - 1] = '\0'; char *args[MAX_ARGS]; int argc = parse_args(cbuf, args); pid_t pid = fork(); if (pid < 0) { perror("fork"); /* fork 失败:关闭所有管道,等待已启动的子进程 */ for (int j = 0; j < npipes; j++) { close(pipes[j][0]); close(pipes[j][1]); } for (int j = 0; j < i; j++) waitpid(pids[j], NULL, 0); return; } if (pid == 0) { /* 子进程: * 关闭所有"不是自己用"的管道端, * 防止 EOF 永远不到达读端。 */ for (int j = 0; j < npipes; j++) { if (pipes[j][0] != in_fd) close(pipes[j][0]); if (pipes[j][1] != out_fd) close(pipes[j][1]); } if (argc == 0) exit(0); exec_cmd_in_child(args, argc, in_fd, out_fd); /* exec_cmd_in_child 内部会 exit,永远不会走到这里 */ } pids[i] = pid; /* 父进程记录子进程 PID */ } /* --- 3. 父进程关闭所有管道端,再统一等待子进程 --- * 必须先全部关闭,否则最后一段命令永远读不到 EOF。 */ for (int i = 0; i < npipes; i++) { close(pipes[i][0]); close(pipes[i][1]); } for (int i = 0; i < nseg; i++) { waitpid(pids[i], NULL, 0); } } void execute(char *cmdline) { char buf[MAX_CMD]; strncpy(buf, cmdline, MAX_CMD - 1); buf[MAX_CMD - 1] = '\0'; char *segments[MAX_PIPES + 1]; int nseg = split_pipeline(buf, segments, MAX_PIPES); if (nseg <= 0) return; /* 空命令或出错 */ if (nseg == 1) { run_single(segments[0]); /* 普通命令,无管道 */ } else { run_pipeline(segments, nseg); /* 多段管道命令 */ } } int main() { char cmdline[MAX_CMD]; while (1) { printf("%% "); fflush(stdout); if (fgets(cmdline, MAX_CMD, stdin) == NULL) { printf("\n"); break; } char *cmd = trim(cmdline); if (strlen(cmd) == 0) continue; if (strcmp(cmd, "exit") == 0 || strcmp(cmd, "logout") == 0) { printf("Goodbye!\n"); break; } execute(cmd); } return 0; }