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

C语言实现Linux下Bash命令管道的问题修复求助

C语言管道机制问题:修复多命令管道连接执行逻辑

问题背景

我需要理解C语言中的管道机制,写了一个Linux下的程序,功能是从stdin读取包含Bash命令的行,将这些命令通过管道连接执行。测试文件cmdfile包含三条命令,用c99编译代码pipeline.c后,运行./pipeline < cmdfile时,程序仅执行第一条ls -s1命令。尝试修改管道逻辑后终端冻结,需要按^C退出,求修复管道和fork的逻辑,让所有命令通过管道串联执行。

测试文件 cmdfile

ls -s1
sort -n
tail -n 5

初始代码 pipeline.c

#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sysexits.h>
#define MAX_ARGS 10
#define MAX_LINE 256
#define MAX_COMMANDS 100

int main() {
    int cmdSize;
    int cmdAmount = 0;
    char line[MAX_LINE];
    int flag = 0;
    char* cmdList[MAX_COMMANDS][MAX_ARGS + 1];
    int i = 0;


    while (fgets(line, 256, stdin)!=NULL && i<MAX_COMMANDS) {

        cmdSize = 0; //reset cmdSize every iter
        cmdAmount++;//keep track of amount of rows in cmdlist

        line[strcspn(line,"\n")] = 0; //strip \n from back of lines cause fgets adds it
        char* token = strtok(line, " ");//tokenise
        char* cmd[11] = {0};

        while(token) {
            cmd[cmdSize] = strdup(token);//strtok returns pointer, so dup value and add to cmd row.

            cmdSize++;
            token = strtok(NULL, " ");
        }
        //add row to 2d array
        memcpy(cmdList[i], cmd, sizeof(cmd));
        i++;
    }
    int pipes[cmdAmount-1][2];
    //pipes
    for (int i = 0; i<cmdAmount; i++) {//iterate thru all rows of cmdList.
        //forks
        pid_t pid;
        pid = fork();
        if (pid < 0) {
        perror("fork error");
        exit(EXIT_FAILURE);
        }
        else if (pid == 0) {
            if (i != 0) {
                close(pipes[i][1]);
                dup2(pipes[i][0],0);
                close(pipes[i][0]);
            }
            if (i+1 != cmdAmount) {
                close(pipes[i+1][0]);
                dup2(pipes[i+1][1],1);
                close(pipes[i+1][1]);
            }

            
            execvp(cmdList[i][0], cmdList[i]); //execute on child
            perror("Exec error");
            exit(EXIT_FAILURE);
            return 0;
        }
        else {
            if (i != 0 ) {
                close(pipes[i][0]);
                close(pipes[i][1]);
            }
            wait(NULL); // wait until child is finished
        }
    }
    return 0;
}

修改后导致终端冻结的代码

#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sysexits.h>
#define MAX_ARGS 10
#define MAX_LINE 256
#define MAX_COMMANDS 100

int main() {
    int cmdSize;
    int cmdAmount = 0;
    char line[MAX_LINE];
    int flag = 0;
    char* cmdList[MAX_COMMANDS][MAX_ARGS + 1];
    int i = 0;


    while (fgets(line, 256, stdin)!=NULL && i<MAX_COMMANDS) {

        cmdSize = 0; //reset cmdSize every iter
        cmdAmount++;//keep track of amount of rows in cmdlist

        line[strcspn(line,"\n")] = 0; //strip \n from back of lines cause fgets adds it
        char* token = strtok(line, " ");//tokenise
        char* cmd[11] = {0};

        while(token) {
            cmd[cmdSize] = strdup(token);//strtok returns pointer, so dup value and add to cmd row.

            cmdSize++;
            token = strtok(NULL, " ");
        }
        //add row to 2d array
        memcpy(cmdList[i], cmd, sizeof(cmd));
        i++;
    }
    //int pipes[cmdAmount-1][2];
    //pipes
    int prev_pipe[2];
    pipe(prev_pipe);
    for (int i = 0; i<cmdAmount; i++) {//iterate thru all rows of cmdList.
        //forks
        int new_pipe[2];
        
        if (i+1 != cmdAmount) {

            pipe(new_pipe);
        }
        //pipe(prev_pipe);
        pid_t pid;
        pid = fork();
        if (pid < 0) {
        perror("fork error");
        exit(EXIT_FAILURE);
        }
        else if (pid == 0) {
            if (i != 0) {
                close(prev_pipe[1]);
                dup2(prev_pipe[0],0);
                close(prev_pipe[0]);
                printf("working1");
            }
            if (i+1 != cmdAmount) {
                close(new_pipe[0]);
                dup2(new_pipe[1],1);
                close(new_pipe[1]);
                printf("working2");
            }

            
            execvp(cmdList[i][0], cmdList[i]); //execute on child
            perror("Exec error");
            exit(EXIT_FAILURE);
            return 0;
        }
        else {

            wait(NULL); // wait until child is finished
            if (i != 0 ) {
                close(prev_pipe[0]);
                close(prev_pipe[1]);
            }
            if (i+1 != cmdAmount) {
                write(prev_pipe[1], &new_pipe[0], sizeof(new_pipe[0]));
            }
        }
    }
    return 0;
}

问题分析

初始代码的核心问题

  1. 未初始化管道:仅声明了管道数组,但未调用pipe()创建任何管道,导致后续管道操作使用无效文件描述符。
  2. 管道索引错误:第i个命令应使用前一个管道pipes[i-1](而非pipes[i]),因为pipes数组长度为cmdAmount-1,对应相邻命令间的连接。
  3. 父进程阻塞时机错误:创建一个子进程后立即wait(NULL),导致命令串行执行,且过早关闭管道,破坏数据流。

修改后代码的核心问题

  1. 错误传递管道描述符:用write()传递管道描述符是完全错误的,管道描述符需通过sendmsg等IPC机制传递,此操作直接导致管道逻辑混乱。
  2. 无效初始管道:开头创建的prev_pipe未被正确使用,父进程后续操作它引发阻塞。

修复方案

正确的管道串联逻辑需遵循以下规则:

  1. 提前创建所有管道:n个命令需要n-1个管道,每个管道连接相邻两个命令。
  2. 子进程管道重定向:
    • 非首个命令:将标准输入重定向到前一个管道的读端。
    • 非末个命令:将标准输出重定向到当前管道的写端。
    • 关闭所有未使用的管道描述符,避免资源泄漏导致阻塞。
  3. 父进程操作:
    • 先创建所有子进程,再统一关闭管道描述符。
    • 所有子进程创建完成后,再等待它们全部结束,确保命令并行运行。

完整修复代码

#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sysexits.h>
#define MAX_ARGS 10
#define MAX_LINE 256
#define MAX_COMMANDS 100

int main() {
    int cmdSize;
    int cmdAmount = 0;
    char line[MAX_LINE];
    char* cmdList[MAX_COMMANDS][MAX_ARGS + 1];
    int i = 0;

    // 读取并解析命令到cmdList
    while (fgets(line, MAX_LINE, stdin) != NULL && i < MAX_COMMANDS) {
        cmdSize = 0;
        cmdAmount++;

        // 去除换行符
        line[strcspn(line, "\n")] = 0;
        char* token = strtok(line, " ");
        char* cmd[MAX_ARGS + 1] = {0};

        while (token != NULL) {
            cmd[cmdSize] = strdup(token);
            cmdSize++;
            token = strtok(NULL, " ");
        }
        memcpy(cmdList[i], cmd, sizeof(cmd));
        i++;
    }

    // 无命令直接退出
    if (cmdAmount == 0) {
        return EXIT_SUCCESS;
    }

    // 创建所有需要的管道
    int pipes[cmdAmount - 1][2];
    for (i = 0; i < cmdAmount - 1; i++) {
        if (pipe(pipes[i]) == -1) {
            perror("pipe error");
            exit(EXIT_FAILURE);
        }
    }

    // 为每个命令创建子进程
    for (i = 0; i < cmdAmount; i++) {
        pid_t pid = fork();
        if (pid < 0) {
            perror("fork error");
            exit(EXIT_FAILURE);
        } else if (pid == 0) {
            // 非首个命令:重定向stdin到前一个管道读端
            if (i != 0) {
                dup2(pipes[i-1][0], STDIN_FILENO);
            }
            // 非末个命令:重定向stdout到当前管道写端
            if (i != cmdAmount - 1) {
                dup2(pipes[i][1], STDOUT_FILENO);
            }

            // 关闭所有管道描述符,避免阻塞
            for (int j = 0; j < cmdAmount - 1; j++) {
                close(pipes[j][0]);
                close(pipes[j][1]);
            }

            // 执行命令
            execvp(cmdList[i][0], cmdList[i]);
            perror("execvp error");
            exit(EXIT_FAILURE);
        }
    }

    // 父进程关闭所有管道描述符
    for (i = 0; i < cmdAmount - 1; i++) {
        close(pipes[i][0]);
        close(pipes[i][1]);
    }

    // 等待所有子进程结束
    while (wait(NULL) != -1);

    // 释放strdup分配的内存,避免泄漏
    for (i = 0; i < cmdAmount; i++) {
        for (int j = 0; cmdList[i][j] != NULL; j++) {
            free(cmdList[i][j]);
        }
    }

    return EXIT_SUCCESS;
}

修复说明

  1. 提前初始化管道:在fork前创建所有需要的管道,确保每个管道都有效。
  2. 正确的管道索引:第i个命令(i>0)读取pipes[i-1]的输出,第i个命令(i<cmdAmount-1)输出到pipes[i]的写端。
  3. 子进程关闭所有管道:完成重定向后关闭所有管道,避免因写端未关闭导致命令阻塞(如tail会等待写端关闭才结束)。
  4. 父进程统一等待:先创建所有子进程,关闭管道后再等待,确保命令并行运行,数据流正常传递。
  5. 内存释放:添加free操作释放strdup分配的内存,避免内存泄漏。

测试验证

编译并运行:

c99 pipeline.c -o pipeline
./pipeline < cmdfile

此时会正确执行ls -s1 | sort -n | tail -n5的效果,输出当前目录下文件大小排序后的最后5行。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 20:50:57