【问题标题】:ls | wc using C doesn't workls |使用 C 的 wc 不起作用
【发布时间】:2023-04-01 22:40:01
【问题描述】:

我编写了一个 C 程序,它使用多个管道来模拟 shell。问题是我可以运行大多数命令,如ls | cat 等,但我无法使用ls | wc。有没有wc不起作用的情况?

int pipefd[4]; 
int p1 = pipe(pipefd);          // Open pipe 1
int p2 = pipe(pipefd + 2);      // Open pipe 2

pid_t pid;

for(i = 0; i < n_commands; i++)
{
    fflush(stdout);
    pid = fork();

    if(pid == 0)
    {
        int command_no = i;
        int prev_pipe = ((command_no - 1) % 2) * 2;
        int current_pipe = (command_no % 2) * 2;

        // If current command is the first command, close the
        // read end, else read from the last command's pipe
        if(command_no == 0)
        {
            close(pipefd[0]);
        }
        else
        {
            dup2(pipefd[prev_pipe], 0);
            close(pipefd[current_pipe]);
        }

        // If current command is the last command, close the
        // write end, else write to the pipe
        if(command_no == n_commands - 1)
        {
            close(pipefd[current_pipe + 1]);
        }
        else
        {
            dup2(pipefd[current_pipe + 1], 1);
        }

        int p = execvp(tokens[cmd_pos[command_no]], tokens + cmd_pos[command_no]);

        close(pipefd[current_pipe]);
        close(pipefd[prev_pipe]);
        close(pipefd[prev_pipe + 1]);
        close(pipefd[current_pipe + 1]);

        _exit(0);
    }
}

如果/usr/bin 中的程序不是管道中的第一个命令,它们似乎不会被执行。

【问题讨论】:

  • 当您尝试从“shell”C 程序中调用此命令时遇到此问题?会发生什么?
  • 是的,在 C 程序中。什么都没有发生,execvp() 也没有返回任何错误。
  • 你能显示一些代码吗?
  • 奇怪 .. 我假设您尝试过其他涉及管道的命令?这些有用吗?如果您自己尝试ls 会怎样?或者,如果您执行 cat somefile | wc 之类的操作 .. 只是想办法缩小问题范围。
  • 请发布能重现此问题的最少资源。就像一个可以证明这个问题的最小 C 程序一样。谢谢。

标签: c pipe


【解决方案1】:

这是一个从您的代码创建的非常简单的程序 - 猜测可能如何创建管道并简化命令 argv 处理:

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

static char *argv_ls[] = { "ls", 0 };
static char *argv_wc[] = { "wc", 0 };
static char **cmds[]   = { argv_ls, argv_wc };

int main(void)
{
    int n_commands = 2;
    int pipefd[2];

    pipe(&pipefd[0]);   // Error check!

    fflush(stdout);
    for (int i = 0; i < n_commands; i++)
    {
        int pid = fork();

        if (pid == 0)
        {
            int command_no = i;
            int prev_pipe = ((command_no - 1) % 2) * 2;
            int current_pipe = (command_no % 2) * 2;
            printf("cmd %d: prev pipe %d, curr pipe %d\n", i, prev_pipe, current_pipe);
            fflush(stdout);

            // If current command is the first command, close the
            // read end, else read from the last command's pipe
            if (command_no == 0)
            {
                close(pipefd[0]);
            }
            else
            {
                dup2(pipefd[prev_pipe], 0);
                close(pipefd[current_pipe]);  // Line 40
            }

            // If current command is the last command, close the
            // write end, else write to the pipe
            if (command_no == n_commands - 1)
                close(pipefd[current_pipe + 1]);  // Line 46
            else
                dup2(pipefd[current_pipe + 1], 1);

            execvp(cmds[i][0], cmds[i]);
            fprintf(stderr, "Failed to exec: %s (%d: %s)\n", cmds[i][0], errno, strerror(errno));
            _exit(1);
        }
    }

    return 0;
}

当 GCC 4.7.1(在 Mac OS X 10.7.4 上)编译它时,它会发出警告:

pipes-12133858.c: In function ‘main’:
pipes-12133858.c:40:22: warning: array subscript is above array bounds [-Warray-bounds]
pipes-12133858.c:46:22: warning: array subscript is above array bounds [-Warray-bounds]

当我运行它时,我得到了输出:

Isis JL: pipes-12133858
cmd 0: prev pipe -2, curr pipe 0
cmd 1: prev pipe 0, curr pipe 2
Isis JL: wc: stdin: read: Bad file descriptor

由于代码中的父进程不等待子进程完成,所以提示出现在来自wc的错误消息之前,但打印的诊断数字显示存在各种问题(并且编译器能够发现一些问题)。

请注意,无需检查任何exec*() 系列函数的返回值。如果成功,则不返回;如果他们回来,他们就失败了。在调用_exit(0); 之前也不需要关闭,因为系统无论如何都会关闭它们。此外,当您执行某些操作失败时,打印一条消息表明您执行失败并以非零退出状态退出是很有礼貌的。

因此,正如Michał Górny 所说,您的问题的主要部分是您的管道处理代码至少是神秘的,因为您没有显示它并且可能是错误的。

我也可以确定您的代码中没有足够的close() 调用。作为指导,在每个打开了管道且将成为管道一部分的进程中,在任何给定的子进程使用exec*() 函数之前,应关闭pipe() 系统调用返回的所有文件描述符。不关闭管道会导致进程挂起,因为管道的写入端是打开的。如果写入端打开的进程是试图从管道的读取端读取的进程,那么它不会找到任何要读取的数据。

【讨论】:

  • int command_no = i 行有问题。你知道我怎样才能访问i吗? (不使用共享内存)
  • @green7:有吗? i 在父子节点中的值是一样的。在第一次迭代中,i 的值为 0,因此command_no 为 0; (0 - 1) % 2-12 * -1-2,如打印值所示。等等,也许你不知道负数和正数的模是负数还是零?
  • 哦,没错。 :) 此外,prev_pipe 的值在第一次迭代中并不重要,因为有代码检查命令是否是第一个命令。此外,还有两个管道:pipefd[0, 1]pipefd[2,3]。第一个命令写入current_pipe+1 = 1,下一个命令从prev_pipe = 0 读取,然后写入stdoutcurrent_pipe+1 = 3
  • 为什么ls | wc 有两个管道?
  • 我正在尝试实现一个通用版本,用于管道超过 1 个的情况。
【解决方案2】:

您的管道连接不正确。

这个逻辑:

int prev_pipe = ((command_no - 1) % 2) * 2;
int current_pipe = (command_no % 2) * 2;

不起作用 - 取模的结果将始终为 01,因此 prev_pipecurrent_pipe 将是 02...

好吧,除非我错过了一些隐藏的概念,因为您没有粘贴任何创建管道的代码。

【讨论】:

  • 我认为一般的想法是,如果你有 N 个命令,就会在一个整数数组中创建 N-1 个管道:int pipefd[2*MAX_PIPELINE]; for (int i = 0; i &lt; N-1; i++) if (pipe(&amp;pipefd[2*i]]) != 0) ...report error...; 我怀疑一般来说没有足够的在代码中关闭;这是一个常见的麻烦来源。但是,在此示例中,应该只有一根管道,这限制了损坏的可能性。通常,当您完成将管道连接到标准输入和标准输出时,应该没有来自 pipe() 的文件描述符保持打开状态。
  • 正如我所说,它支持多个管道。我已经声明了两个管道pipefd[0,1]pipefd[2,3],不管有多少管道,它们都会负责通信。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-07-30
  • 1970-01-01
  • 2014-03-02
  • 2017-07-26
  • 1970-01-01
  • 2016-07-02
相关资源
最近更新 更多