管道间重定向异常:模拟Unix多管道命令无输出问题排查
模拟管道命令的C程序无输出问题排查与修复
问题描述
编写了一个模拟Unix命令seq 2 number | awk argument | grep argument的C程序,运行./program 26 {print} 5时无任何输出,正确输出应为:
5 15 25
但移除awk或grep其中一个环节时,程序运行符合预期。原程序代码如下:
#define READ 0 #define WRITE 1 void die(char* msg){ perror(msg); exit(EXIT_FAILURE); } int main(int argc, char *argv[]) { int seq_to_awk[2]; if(pipe(seq_to_awk) == -1) die("pipe"); pid_t child = fork(); if(child == -1) die("fork"); if(child == 0){ if(close(seq_to_awk[READ]) == -1) die("close"); if(dup2(seq_to_awk[WRITE], STDOUT_FILENO) < 0) die("dup2"); if(close(seq_to_awk[WRITE]) == -1) die("close"); execlp("seq", "seq", "2", argv[1], NULL); } child = fork(); if(child == -1) die("fork"); int awk_to_grep[2]; if(pipe(awk_to_grep) == -1) die("pipe"); if(child == 0){ if(close(seq_to_awk[WRITE]) == -1) die("close"); if(dup2(seq_to_awk[READ], STDIN_FILENO) < 0) die("dup2"); if(close(seq_to_awk[READ]) == -1) die("close"); /**/ if(close(awk_to_grep[READ]) < 0) die("close"); if(dup2(awk_to_grep[WRITE], STDOUT_FILENO) < 0) die("dup2"); if(close(awk_to_grep[WRITE]) == -1) die("close"); /**/ execlp("awk", "awk", argv[2], NULL); die("execlp"); } if(close(seq_to_awk[READ]) == -1) die("close"); if(close(seq_to_awk[WRITE]) == -1) die("close"); /**/ child = fork(); if(child == -1) die("fork"); if(child == 0){ if(close(awk_to_grep[WRITE]) == -1) die("close"); if(dup2(awk_to_grep[READ], STDIN_FILENO) < 0) die("dup2"); if(close(awk_to_grep[READ]) == -1) die("close"); execlp("grep", "grep", argv[3], NULL); die("execlp"); } /**/ if(close(awk_to_grep[READ]) == -1) die("close"); if(close(awk_to_grep[WRITE]) == -1) die("close"); wait(NULL); wait(NULL); wait(NULL); exit(EXIT_SUCCESS); }
问题根源
核心错误是**awk_to_grep管道的创建时机晚于第二个fork操作**:
- 在fork出awk子进程之后才创建
awk_to_grep管道,导致awk子进程继承的是未初始化的管道描述符,无法将输出正确写入管道。 - 父进程后续关闭管道的操作会直接切断管道,使得grep进程无法读取到任何数据,最终无输出。
正确逻辑是:所有需要父子进程共享的管道,必须在fork之前创建,这样子进程才能继承到有效的管道描述符。
修复后的代码
#define READ 0 #define WRITE 1 void die(char* msg){ perror(msg); exit(EXIT_FAILURE); } int main(int argc, char *argv[]) { int seq_to_awk[2]; if(pipe(seq_to_awk) == -1) die("pipe"); pid_t child = fork(); if(child == -1) die("fork"); if(child == 0){ if(close(seq_to_awk[READ]) == -1) die("close"); if(dup2(seq_to_awk[WRITE], STDOUT_FILENO) < 0) die("dup2"); if(close(seq_to_awk[WRITE]) == -1) die("close"); execlp("seq", "seq", "2", argv[1], NULL); die("execlp"); // 新增:execlp失败后报错退出 } // 先创建awk_to_grep管道,再fork awk子进程 int awk_to_grep[2]; if(pipe(awk_to_grep) == -1) die("pipe"); child = fork(); if(child == -1) die("fork"); if(child == 0){ if(close(seq_to_awk[WRITE]) == -1) die("close"); if(dup2(seq_to_awk[READ], STDIN_FILENO) < 0) die("dup2"); if(close(seq_to_awk[READ]) == -1) die("close"); if(close(awk_to_grep[READ]) < 0) die("close"); if(dup2(awk_to_grep[WRITE], STDOUT_FILENO) < 0) die("dup2"); if(close(awk_to_grep[WRITE]) == -1) die("close"); execlp("awk", "awk", argv[2], NULL); die("execlp"); } if(close(seq_to_awk[READ]) == -1) die("close"); if(close(seq_to_awk[WRITE]) == -1) die("close"); child = fork(); if(child == -1) die("fork"); if(child == 0){ if(close(awk_to_grep[WRITE]) == -1) die("close"); if(dup2(awk_to_grep[READ], STDIN_FILENO) < 0) die("dup2"); if(close(awk_to_grep[READ]) == -1) die("close"); execlp("grep", "grep", argv[3], NULL); die("execlp"); } if(close(awk_to_grep[READ]) == -1) die("close"); if(close(awk_to_grep[WRITE]) == -1) die("close"); wait(NULL); wait(NULL); wait(NULL); exit(EXIT_SUCCESS); }
额外优化说明
- 在每个
execlp调用后添加die函数:若execlp执行失败(如命令不存在),会直接报错退出,避免进入后续无效代码。 - 严格遵循“先创管道,再fork子进程”的顺序:确保子进程能继承到有效的管道描述符,保证数据流正常传递。
内容的提问来源于stack exchange,提问作者yad0
相关产品推荐
相关产品推荐

