You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何补全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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 19:17:08