如何补全C代码循环逻辑实现管道命令多进程执行?
补全管道命令执行的while循环逻辑(附dup2详解)
先明确核心需求:将|分隔的命令序列逐个在子进程中执行,通过管道把前一个命令的输出作为后一个的输入。下面直接给出完整可运行代码,并重点拆解while循环内部的逻辑,同时解释dup2的实际用法。
完整代码实现
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <sys/wait.h> // 辅助函数:按|分割命令序列 int split_commands(const char *input, char ***commands) { char *buf = strdup(input); char *token = strtok(buf, "|"); int count = 0; char **cmds = NULL; while (token != NULL) { // 去除命令前后空格 while (*token == ' ') token++; char *end = token + strlen(token) - 1; while (end > token && *end == ' ') end--; *(end + 1) = '\0'; cmds = realloc(cmds, sizeof(char*) * (count + 1)); cmds[count++] = strdup(token); token = strtok(NULL, "|"); } *commands = cmds; free(buf); return count; } // 辅助函数:将单个命令分割为argv数组(如"ls -a"转为{"ls", "-a", NULL}) char **split_args(const char *cmd) { char *buf = strdup(cmd); char *token = strtok(buf, " "); int count = 0; char **argv = NULL; while (token != NULL) { argv = realloc(argv, sizeof(char*) * (count + 1)); argv[count++] = strdup(token); token = strtok(NULL, " "); } argv = realloc(argv, sizeof(char*) * (count + 1)); argv[count] = NULL; free(buf); return argv; } int main() { char input[1024]; printf("Enter command sequence: "); fgets(input, sizeof(input), stdin); input[strcspn(input, "\n")] = '\0'; // 移除换行符 char **commands; int num_commands = split_commands(input, &commands); if (num_commands == 0) { fprintf(stderr, "No commands entered\n"); return 1; } int prev_fd = 0; // 初始输入为标准输入(STDIN_FILENO=0) int i = 0; // --- 核心while循环开始 --- while (i < num_commands) { int pipefd[2]; // 非最后一个命令需要创建管道,用于传递输出给下一个命令 if (i < num_commands - 1) { if (pipe(pipefd) == -1) { perror("pipe"); exit(EXIT_FAILURE); } } pid_t pid = fork(); if (pid == -1) { perror("fork"); exit(EXIT_FAILURE); } else if (pid == 0) { // ------------------------ // 子进程逻辑:执行当前命令,处理输入输出重定向 // ------------------------ // 1. 重定向输入:把标准输入替换为prev_fd(上一个命令的输出/初始stdin) if (dup2(prev_fd, STDIN_FILENO) == -1) { perror("dup2 stdin"); exit(EXIT_FAILURE); } close(prev_fd); // 重定向后原fd无用,关闭避免泄漏 // 2. 非最后一个命令:重定向输出到管道写端 if (i < num_commands - 1) { if (dup2(pipefd[1], STDOUT_FILENO) == -1) { perror("dup2 stdout"); exit(EXIT_FAILURE); } close(pipefd[0]); // 子进程不需要管道读端 close(pipefd[1]); // 重定向后原写端无用,关闭 } // 3. 分割命令为argv并执行 char **argv = split_args(commands[i]); execvp(argv[0], argv); // 若execvp返回,说明执行失败 perror("execvp"); exit(EXIT_FAILURE); } else { // ------------------------ // 父进程逻辑:清理资源,更新下一个命令的输入fd // ------------------------ close(prev_fd); // 父进程不再需要上一个输入fd if (i < num_commands - 1) { close(pipefd[1]); // 父进程不需要管道写端 prev_fd = pipefd[0]; // 下一个命令的输入设为管道读端 } wait(NULL); // 等待当前子进程执行完毕,避免僵尸进程 } i++; } // --- 核心while循环结束 --- // 释放资源 for (int j = 0; j < num_commands; j++) { free(commands[j]); } free(commands); return 0; }
while循环核心逻辑拆解
1. 管道创建判断
只有当前命令不是最后一个时,才调用pipe(pipefd)创建管道:
pipefd[0]是管道读端(用于读取数据)pipefd[1]是管道写端(用于写入数据)
最后一个命令的输出直接指向屏幕,无需管道。
2. 子进程输入输出重定向(重点)
dup2(oldfd, newfd)的作用是:关闭newfd(如果已打开),将oldfd的文件描述符复制给newfd,后续操作newfd等价于操作oldfd。这是实现管道的核心:
- 输入重定向:
dup2(prev_fd, STDIN_FILENO)把子进程的标准输入(原本是键盘)替换为prev_fd(初始是标准输入,后续是上一个管道的读端),这样命令会从prev_fd读取输入。 - 输出重定向:非最后一个命令执行
dup2(pipefd[1], STDOUT_FILENO),把子进程的标准输出(原本是屏幕)替换为管道写端,命令的输出会直接写入管道,供下一个命令读取。
3. 父进程资源管理
父进程不需要保留子进程已经使用的文件描述符:
- 关闭
prev_fd,因为子进程已经接手处理这个输入流 - 如果创建了管道,关闭写端
pipefd[1],只保留读端pipefd[0]作为下一个命令的输入源 - 调用
wait(NULL)等待子进程执行完成,避免产生僵尸进程
内容的提问来源于stack exchange,提问作者Amir Jafari
相关产品推荐
相关产品推荐

