#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; } /* 把命令字符串按空格拆成 args[],返回参数个数 */ 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; } /* ========================================================= * 第一步辅助函数:split_pipeline * 把 "cmd1 | cmd2 | cmd3" 按 '|' 拆成若干段, * 存入 segments[],返回段数。 * 注意:会修改 buf 的内容(把 '|' 替换为 '\0')。 * ========================================================= */ 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; } /* ========================================================= * 第二步辅助函数:exec_cmd_in_child * 在子进程里执行一条命令,处理 < 和 > 重定向。 * in_fd / out_fd 来自管道(或 STDIN/STDOUT_FILENO)。 * 此函数只能在 fork() 后的子进程里调用,内部会 exit()。 * ========================================================= */ static void exec_cmd_in_child(char **args, int argc, int in_fd, int out_fd) { /* --- 解析重定向符,把 < file 和 > file 从参数里剥离 --- */ 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); /* --- 设置标准输入 --- * 优先级:< 文件重定向 > 管道读端 > 默认终端 stdin */ 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); } /* --- 设置标准输出 --- * 优先级:> 文件重定向 > 管道写端 > 默认终端 stdout */ 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); } /* ========================================================= * 第三步辅助函数:run_single * 执行单条命令(无管道),fork 一个子进程并等待它结束。 * ========================================================= */ 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); } /* ========================================================= * 第四步辅助函数:run_pipeline * 执行多段管道命令。 * * 核心思路(以 "A | B | C" 为例): * * 终端stdin ──> [A] ──pipe0──> [B] ──pipe1──> [C] ──> 终端stdout * * 1. 创建 nseg-1 个管道(pipe0, pipe1, ...) * 2. 依次 fork 每段命令的子进程,子进程拿到自己的 in_fd/out_fd * 3. 父进程关闭所有管道端,然后等待全部子进程结束 * ========================================================= */ 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); } } /* ========================================================= * 顶层入口:execute * 只做"分流": * - 把命令行按 '|' 分段 * - 1 段 → run_single * - 多段 → run_pipeline * ========================================================= */ 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); /* 多段管道命令 */ } } /* ========================================================= * main:读取命令行,循环执行 * ========================================================= */ 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; }