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

自定义多管道Shell故障求助:execute_pipeline函数疑似异常

自定义Shell多管道功能故障排查

问题现象

使用C语言开发支持多管道的自定义Shell,单命令(如ls -l)可正常执行,但带管道的命令无法运行。执行cat check.txt | sort | uniq > cmp_sort_uniq.txt时出现以下错误:

sort: read failed: -: Bad file descriptor
uniq: '>': No such file or directory

怀疑问题出在execute_pipeline函数中,可能是管道未及时关闭或文件描述符传递错误。

相关代码

execute_pipeline函数

void execute_pipeline(char *args[MAX_ARGS], int n_args)
{
    char *pipe_args[MAX_PIPES][MAX_ARGS];
    int pipe_fds[MAX_PIPES][2];
    int n_pipes = 0, n_comm = 0;
    memset(pipe_args, 0, sizeof(pipe_args));
    for (int i = 0; i < n_args; i++)
    {
        if (strcmp(args[i], "|") == 0)
        {
            if (n_pipes >= MAX_PIPES)
            {
                fprintf(stderr, "Error: too many pipes\n");
                return;
            }
            n_pipes++;
            n_comm = 0;
        }
        else
        {
            pipe_args[n_pipes][n_comm % MAX_ARGS] = args[i];
            pipe_args[n_pipes][n_comm % MAX_ARGS + 1] = NULL;
            n_comm++;
        }
    }
    memset(pipe_fds,0,sizeof(pipe_fds));
    // Create pipes
    for (int i = 0; i <= n_pipes; i++)
    {
        if (pipe(pipe_fds[i]) == -1)
        {
            perror("pipe");
            return;
        }
        // Determine inputfds
        int input_fd = (i == 0)? STDIN_FILENO : pipe_fds[i-1][1];
        // Execute command
        execute_command(pipe_args[i], input_fd, pipe_fds[i][1]);
        // Close pipe fds if not needed
        if (i>0)
        {
            close(pipe_fds[i-1][0]);
            close(pipe_fds[i-1][1]);
        }
    }  
}

execute_command函数

void execute_command(char *args[MAX_ARGS], int input_fd, int output_fd)
{
    // Fork to execute command
    pid_t pid = fork();

    if (pid == 0)
    { // Child process
        // Set signal handler for SIGINT (Ctrl+C)
        signal(SIGINT, SIG_DFL);

        // Redirect input/output if necessary
        if (input_fd != STDIN_FILENO)
        {
            if (dup2(input_fd, STDIN_FILENO) < 0)
            {
                perror("dup2");
                exit(1);
            }
            close(input_fd);
        }
        if (output_fd != STDOUT_FILENO)
        {
            if (dup2(output_fd, STDOUT_FILENO) < 0)
            {
                perror("dup2");
                exit(1);
            }
            close(output_fd);
        }

        // Execute command
        execvp(args[0], args);
        perror("execvp");
        exit(1);
    }
    else if (pid < 0)
    {
        perror("fork");
        exit(1);
    }

    // Wait for child process to complete
    int status;
    wait(&status);
}

问题分析与修复方案

1. 核心错误点

  • 管道文件描述符误用:管道的读端是pipe_fds[i][0],写端是pipe_fds[i][1],原代码将前一个管道的写端作为下一个命令的输入,完全颠倒了读写方向。
  • 多余管道创建:n_pipes个管道对应n_pipes+1个命令,原代码循环创建n_pipes+1个管道,最后一个管道无意义,导致最后一个命令输出被写入无效管道。
  • 重定向未解析:>符号被当作uniq的参数传递,说明Shell未在管道拆分前处理重定向逻辑。
  • 管道关闭时机不当:父进程过早关闭管道描述符,可能导致子进程未完成读写就报错。

2. 修复后的execute_pipeline函数

void execute_pipeline(char *args[MAX_ARGS], int n_args)
{
    char *pipe_args[MAX_PIPES][MAX_ARGS];
    int pipe_fds[MAX_PIPES][2];
    int n_pipes = 0, n_comm = 0;
    int output_fd = STDOUT_FILENO;

    // 提前处理输出重定向(简单实现,仅支持最后一个>)
    for (int i = 0; i < n_args; i++) {
        if (strcmp(args[i], ">") == 0) {
            if (i + 1 >= n_args) {
                fprintf(stderr, "Error: missing filename after >\n");
                return;
            }
            int fd = open(args[i+1], O_WRONLY | O_CREAT | O_TRUNC, 0644);
            if (fd == -1) {
                perror("open");
                return;
            }
            output_fd = fd;
            args[i] = NULL;
            n_args = i;
            break;
        }
    }

    // 拆分管道命令
    memset(pipe_args, 0, sizeof(pipe_args));
    for (int i = 0; i < n_args; i++)
    {
        if (strcmp(args[i], "|") == 0)
        {
            if (n_pipes >= MAX_PIPES)
            {
                fprintf(stderr, "Error: too many pipes\n");
                return;
            }
            n_pipes++;
            n_comm = 0;
        }
        else
        {
            pipe_args[n_pipes][n_comm] = args[i];
            pipe_args[n_pipes][n_comm + 1] = NULL;
            n_comm++;
        }
    }

    // 创建所有需要的管道
    for (int i = 0; i < n_pipes; i++)
    {
        if (pipe(pipe_fds[i]) == -1)
        {
            perror("pipe");
            // 清理已创建的管道
            for (int j = 0; j < i; j++) {
                close(pipe_fds[j][0]);
                close(pipe_fds[j][1]);
            }
            return;
        }
    }

    // 启动所有命令进程
    for (int i = 0; i <= n_pipes; i++)
    {
        int input_fd = (i == 0) ? STDIN_FILENO : pipe_fds[i-1][0];
        int cmd_output_fd = (i == n_pipes) ? output_fd : pipe_fds[i][1];
        
        execute_command(pipe_args[i], input_fd, cmd_output_fd);
        
        // 父进程关闭当前不需要的管道描述符
        if (i > 0) {
            close(pipe_fds[i-1][0]);
            close(pipe_fds[i-1][1]);
        }
        if (i == n_pipes && output_fd != STDOUT_FILENO) {
            close(output_fd);
        }
    }
}

3. 额外优化建议

  • 并行执行管道命令:原execute_command中的wait会导致命令串行执行,可改为在execute_pipeline最后用waitpid循环等待所有子进程,提升管道效率。
  • 完善重定向处理:增加对<、>>等重定向符号的支持,在命令解析阶段完成重定向逻辑,避免参数污染。

内容的提问来源于stack exchange,提问作者Achiya Ben Natan

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 01:08:08