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

在C程序中通过管道实现父进程控制execve创建的Bash子进程

嘿,这个需求本质上就是实现一个简单的父进程代理shell的功能,我给你写一个完整的可运行C代码,再把核心逻辑拆解开讲清楚,保证你能理解每个环节:

完整可运行代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>

#define BUF_SIZE 1024

int main() {
    int parent_to_child[2];  // 父进程写 → 子进程读:用来给Bash发命令
    int child_to_parent[2];  // 子进程写 → 父进程读:用来接收Bash的输出
    pid_t pid;
    char buf[BUF_SIZE];
    ssize_t n_read;

    // 创建两个单向管道
    if (pipe(parent_to_child) == -1 || pipe(child_to_parent) == -1) {
        perror("pipe failed");
        exit(EXIT_FAILURE);
    }

    // 分叉创建子进程
    pid = fork();
    if (pid == -1) {
        perror("fork failed");
        exit(EXIT_FAILURE);
    }

    if (pid == 0) {  // 子进程:启动Bash并绑定管道IO
        // 关闭子进程用不到的管道端(必须关,否则会导致死锁或资源泄漏)
        close(parent_to_child[1]);
        close(child_to_parent[0]);

        // 将Bash的标准输入重定向到父→子管道的读端
        if (dup2(parent_to_child[0], STDIN_FILENO) == -1) {
            perror("dup2 stdin failed");
            exit(EXIT_FAILURE);
        }
        // 将Bash的标准输出重定向到子→父管道的写端
        if (dup2(child_to_parent[1], STDOUT_FILENO) == -1) {
            perror("dup2 stdout failed");
            exit(EXIT_FAILURE);
        }
        // 可选:把标准错误也重定向过来,这样错误信息也能传回父进程
        if (dup2(child_to_parent[1], STDERR_FILENO) == -1) {
            perror("dup2 stderr failed");
            exit(EXIT_FAILURE);
        }

        // 重定向完成后,关闭原来的管道描述符(已经被dup2复制了,留着没用)
        close(parent_to_child[0]);
        close(child_to_parent[1]);

        // 调用execve启动Bash
        // 注意:execve需要绝对路径,参数数组必须以NULL结尾
        char *args[] = {"/bin/bash", NULL};
        execve("/bin/bash", args, NULL);

        // 如果走到这里,说明execve执行失败了
        perror("execve failed");
        exit(EXIT_FAILURE);
    } else {  // 父进程:读取用户输入→发给Bash,接收Bash输出→打印到终端
        // 关闭父进程用不到的管道端
        close(parent_to_child[0]);
        close(child_to_parent[1]);

        printf("Enter command (type 'exit' to quit):\n");
        while (1) {
            // 打印提示符并读取用户输入
            printf("> ");
            fflush(stdout);  // 强制刷新输出缓冲区,确保提示符及时显示
            n_read = read(STDIN_FILENO, buf, BUF_SIZE);
            if (n_read == -1) {
                perror("read stdin failed");
                break;
            }
            if (n_read == 0) {  // 用户按下Ctrl+D,结束输入
                break;
            }

            // 检查用户是否输入exit,是的话退出循环
            if (strncmp(buf, "exit\n", 5) == 0 || strncmp(buf, "exit", 4) == 0) {
                break;
            }

            // 将输入的命令写入管道,发给Bash
            if (write(parent_to_child[1], buf, n_read) == -1) {
                perror("write to child failed");
                break;
            }

            // 读取Bash的输出并打印到父进程的标准输出
            memset(buf, 0, BUF_SIZE);
            while ((n_read = read(child_to_parent[0], buf, BUF_SIZE)) > 0) {
                write(STDOUT_FILENO, buf, n_read);
                memset(buf, 0, BUF_SIZE);
            }
            if (n_read == -1) {
                perror("read from child failed");
                break;
            }
        }

        // 关闭管道,通知Bash结束
        close(parent_to_child[1]);
        close(child_to_parent[0]);

        // 等待子进程退出,避免产生僵尸进程
        waitpid(pid, NULL, 0);
        printf("\nChild Bash process exited.\n");
    }

    return 0;
}
核心逻辑拆解
  • 管道设计:我们需要两个单向管道,而不是一个双向管道——因为进程间通信是单向流式的,一个管父发命令,一个管子回输出,这样逻辑更清晰,也避免冲突。
  • IO重定向:子进程必须把Bash的标准输入/输出绑定到管道,否则Bash还是会默认从终端读、往终端写,达不到我们的控制目的。这里用dup2系统调用完成重定向,它会把指定的文件描述符替换成目标描述符(比如把parent_to_child[0]替换成STDIN_FILENO)。
  • execve注意事项:execve要求传入程序的绝对路径(比如/bin/bash),不能直接写bash(那是execlp这类函数的功能);另外参数数组必须以NULL结尾,这是exec系列函数的要求。
  • 死锁避免:一定要关闭不需要的管道端!比如父进程不需要读parent_to_child的读端,就必须关掉它,否则系统会认为管道的读端还在打开,可能导致写端的进程一直等待。
  • 退出处理:父进程退出前要关闭管道,这样Bash会因为输入流关闭而退出;然后用waitpid等待子进程结束,防止僵尸进程残留。

内容的提问来源于stack exchange,提问作者Narc Eritor

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 04:07:10