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

编译运行./prog ls ls wc 1.txt时程序停滞,需按Ctrl-D才完成的原因

程序执行停滞的原因分析与修复

你遇到的这个问题其实是管道使用中的典型陷阱——咱们先把场景和问题拆解清楚,再讲怎么解决。

首先看你的执行需求:运行./prog ls ls wc 1.txt,想要实现(ls; ls) | wc > 1.txt的效果,但程序卡住不动,必须按Control-D才能完成。先看看你的核心代码逻辑(虽然原代码有截断,但关键问题点已经能看出来):

#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>

int main(int argc, char const *argv[]) {
    int fd[2];
    pipe(fd);
    if (!fork()) {
        close(fd[0]);
        dup2(fd[1], 1);
        close(fd[1]);
        if (!(fork())) {
            execlp(argv[1], argv[1], NULL);
            // 原代码截断,但核心逻辑是要执行两个ls
        }
        // 第一个子进程(P1)的后续逻辑缺失
    } else {
        close(fd[1]);
        dup2(fd[0], 0);
        close(fd[0]);
        int fd_file = open(argv[4], O_WRONLY | O_CREAT | O_TRUNC, 0644);
        dup2(fd_file, 1);
        close(fd_file);
        execlp(argv[3], argv[3], NULL);
    }
    wait(NULL);
    return 0;
}

为什么程序会停滞?

核心原因是管道的写端没有被完全关闭,导致wc进程一直阻塞等待输入。

咱们一步步理执行流程:

  1. 主进程创建管道,fork出第一个子进程P1。
  2. P1关闭管道读端,把标准输出重定向到管道写端,接着又fork出子进程P2来执行第一个ls。
  3. 问题来了:P1在fork出P2之后,既没有执行第二个ls,也没有退出,更没有主动关闭管道写端——它就这么挂着,一直占着管道的写端。
  4. 主进程这边已经把标准输入重定向到管道读端,执行wc命令。而wc的特性是:会持续读取输入,直到所有管道写端都被关闭,才会停止读取并处理输出。

现在P2执行完ls退出后,P1还活着,并且握着管道写端没放,wc就会一直等着,以为还会有数据从管道过来。直到你按Control-D(手动关闭主进程的标准输入),或者P1关闭写端/退出,wc才会结束。

怎么修复?

你需要确保所有不需要管道写端的进程都关闭它,同时让P1正确完成第二个ls的执行后退出,自动释放管道写端。修改后的核心代码如下:

#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>

int main(int argc, char const *argv[]) {
    if (argc != 5) {
        fprintf(stderr, "Usage: %s cmd1 cmd2 out_cmd out_file\n", argv[0]);
        exit(1);
    }

    int fd[2];
    if (pipe(fd) == -1) {
        perror("pipe failed");
        exit(1);
    }

    pid_t pid1 = fork();
    if (pid1 == -1) {
        perror("fork pid1 failed");
        exit(1);
    }

    if (pid1 == 0) {
        // 子进程P1:负责执行两个ls,输出到管道
        close(fd[0]); // 关闭读端,不需要
        if (dup2(fd[1], STDOUT_FILENO) == -1) {
            perror("dup2 stdout failed");
            exit(1);
        }
        close(fd[1]); // 重定向完成后关闭原写端

        // 执行第一个ls
        pid_t pid2 = fork();
        if (pid2 == -1) {
            perror("fork pid2 failed");
            exit(1);
        }
        if (pid2 == 0) {
            execlp(argv[1], argv[1], NULL);
            perror("execlp cmd1 failed");
            exit(1);
        }
        // 等待第一个ls执行完成
        wait(NULL);

        // 执行第二个ls
        execlp(argv[2], argv[2], NULL);
        perror("execlp cmd2 failed");
        exit(1);
    } else {
        // 主进程:负责执行wc,输出到文件
        close(fd[1]); // 关闭写端,不需要
        if (dup2(fd[0], STDIN_FILENO) == -1) {
            perror("dup2 stdin failed");
            exit(1);
        }
        close(fd[0]); // 重定向完成后关闭原读端

        // 打开输出文件
        int fd_file = open(argv[4], O_WRONLY | O_CREAT | O_TRUNC, 0644);
        if (fd_file == -1) {
            perror("open out file failed");
            exit(1);
        }
        if (dup2(fd_file, STDOUT_FILENO) == -1) {
            perror("dup2 stdout to file failed");
            exit(1);
        }
        close(fd_file);

        // 执行wc
        execlp(argv[3], argv[3], NULL);
        perror("execlp out_cmd failed");
        exit(1);
    }

    wait(NULL);
    return 0;
}

修复后的关键变化:

  1. P1在fork出P2后,等待P2执行完第一个ls,然后自己执行第二个ls,执行完成后自动退出,内核会自动关闭它持有的管道写端。
  2. 所有进程在完成管道重定向后,都主动关闭了原管道描述符,避免资源泄漏。
  3. 增加了错误处理,让程序更健壮。

这样修改后,当两个ls都执行完成退出后,管道的所有写端都被关闭,wc就会停止等待输入,处理完数据后退出,程序就不会再停滞了。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.22 10:01:05