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; }
问题分析
初始代码的核心问题
- 未初始化管道:仅声明了管道数组,但未调用
pipe()创建任何管道,导致后续管道操作使用无效文件描述符。 - 管道索引错误:第
i个命令应使用前一个管道pipes[i-1](而非pipes[i]),因为pipes数组长度为cmdAmount-1,对应相邻命令间的连接。 - 父进程阻塞时机错误:创建一个子进程后立即
wait(NULL),导致命令串行执行,且过早关闭管道,破坏数据流。
修改后代码的核心问题
- 错误传递管道描述符:用
write()传递管道描述符是完全错误的,管道描述符需通过sendmsg等IPC机制传递,此操作直接导致管道逻辑混乱。 - 无效初始管道:开头创建的
prev_pipe未被正确使用,父进程后续操作它引发阻塞。
修复方案
正确的管道串联逻辑需遵循以下规则:
- 提前创建所有管道:n个命令需要n-1个管道,每个管道连接相邻两个命令。
- 子进程管道重定向:
- 非首个命令:将标准输入重定向到前一个管道的读端。
- 非末个命令:将标准输出重定向到当前管道的写端。
- 关闭所有未使用的管道描述符,避免资源泄漏导致阻塞。
- 父进程操作:
- 先创建所有子进程,再统一关闭管道描述符。
- 所有子进程创建完成后,再等待它们全部结束,确保命令并行运行。
完整修复代码
#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; }
修复说明
- 提前初始化管道:在fork前创建所有需要的管道,确保每个管道都有效。
- 正确的管道索引:第
i个命令(i>0)读取pipes[i-1]的输出,第i个命令(i<cmdAmount-1)输出到pipes[i]的写端。 - 子进程关闭所有管道:完成重定向后关闭所有管道,避免因写端未关闭导致命令阻塞(如
tail会等待写端关闭才结束)。 - 父进程统一等待:先创建所有子进程,关闭管道后再等待,确保命令并行运行,数据流正常传递。
- 内存释放:添加
free操作释放strdup分配的内存,避免内存泄漏。
测试验证
编译并运行:
c99 pipeline.c -o pipeline ./pipeline < cmdfile
此时会正确执行ls -s1 | sort -n | tail -n5的效果,输出当前目录下文件大小排序后的最后5行。
内容的提问来源于stack exchange,提问作者user23590427
相关产品推荐
相关产品推荐

