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

管道间重定向异常:模拟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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 01:55:17