【问题标题】:trying to run "ls | grep r" with "execvp()"尝试使用“execvp()”运行“ls | grep r”
【发布时间】:2012-12-03 23:09:52
【问题描述】:
  1. 我在两个子进程之间创建了一个pipe, 首先,我运行ls,它会写入正确的 fd, 然后,我运行grep r,它从正确的 fd 中读取,

  2. 我可以在终端中看到grep 命令工作正常(输出)

  3. 问题是grep 没有退出,它一直留在那里,即使ls 不再运行

对于其他程序,pipe 工作正常..

for (i = 0; i < commands_num ; i++) {   //exec all the commands instants
    if (pcommands[i]._flag_pipe_out == 1) { //creates pipe if necessary 
        if (pipe(pipe_fd) == -1) {
            perror("Error: \"pipe()\" failed");
        }
        pcommands[i]._fd_out = pipe_fd[1];
        pcommands[i+1]._fd_in = pipe_fd[0]; 
    }
    pid = fork();   //the child exec the commands  
    if (pid == -1) {
        perror("Error: \"fork()\" failed");
        break;          
    } else if (!pid) { //child process

        if (pcommands[i]._flag_pipe_in == 1) {  //if there was a pipe to this command
            if (dup2(pcommands[i]._fd_in, STDIN) == -1) {
                perror("Error: \"dup2()\" failed");
                exit(0);
            }
            close(pcommands[i]._fd_in);
        }

        if (pcommands[i]._flag_pipe_out == 1) { //if there was a pipe from this command
            if (dup2(pcommands[i]._fd_out, STDOUT) == -1) {
                perror("Error: \"dup2()\" failed");
                exit(0);
            }
            close(pcommands[i]._fd_out);
        } 
        execvp(pcommands[i]._commands[0] , pcommands[i]._commands); //run the command

        perror("Error: \"execvp()\" failed");
        exit(0);
    } else if (pid > 0) { //father process
    waitpid(pid, NULL, WUNTRACED);
    }
}
//closing all the open fd's
for (i = 0; i < commands_num ; i++) {
    if (pcommands[i]._fd_in != STDIN) { //if there was an other stdin that is not 0
        close(pcommands[i]._fd_in);
    }           
    if (pcommands[i]._fd_out != STDOUT) { //if there was an other stdout that is not 1
        close(pcommands[i]._fd_out);            
    }   
}

所以,我有一个“命令”即时pcommands[i] 它有: pipein,pipeout 的标志 fdin,fdout, 和一个 char**(对于真正的命令,如“ls -l”)

让我们说一切都很好, 这意味着:

pcommands[0]:
pipein=0
pipeout=1
char** = {"ls","-l",NULL}

pcommands[1]:
pipein=1
pipeout=0
char** = {"grep","r",NULL}

现在,循环将进行两次(因为我有两个命令瞬间) 第一次,它会看到 pcommands[0] 有 pipeout==1 创建管道 做叉子 pcommands[0] 有 pipeout==1 孩子:dup2 到标准输出 执行程序

第二次: 不创建管道 做叉子 孩子: pcomands[1] 有 pipein==1 然后: dup2 到输入 执行 ..

这个命令有效,我的输出是:

errors.log exer2.pdf multipal_try

(所有带有'r'的东西) 但后来它卡住了,并没有离开grep.. 在另一个终端我可以看到grep 仍在工作

我希望我关闭所有我需要关闭的 fd...

我不明白为什么它不起作用,看来我做得对(嗯,它适用于其他命令..)

有人可以帮忙吗?谢谢

【问题讨论】:

  • 你需要接受你过去的一些问题!
  • 这是什么意思?我该怎么做?
  • 在过去的问题中,如果您认为答案正确,则需要通过单击答案有多少票附近的绿色勾号来接受答案。如果人们看到您不接受您的任何答案,那么他们就不会想要回答您的问题。
  • 是的,我不知道...我只是想我应该用上箭头给分数...谢谢!

标签: c


【解决方案1】:

您没有关闭足够多的管道文件描述符。

经验法则:

  • 如果您使用dup()dup2() 将管道文件描述符复制到标准输入或标准输出,您应该关闭两个原始管道文件描述符。

您还需要确保如果父 shell 创建管道,它会关闭其管道文件描述符的两个副本。

还请注意,应允许管道中的进程同时运行。特别是,管道的容量有限,当管道中没有剩余空间时,进程会阻塞。限制可能非常小(POSIX 要求它必须至少为 4 KiB,但仅此而已)。如果您的程序处理兆字节的数据,则必须允许它们在管道中同时运行。因此,waitpid() 应该出现在启动子项的循环之外。您还需要在等待之前关闭父进程中的管道;否则,读取管道的子进程将永远不会看到 EOF(因为理论上父进程可以写入管道,即使它不会)。

您有名称以下划线开头的结构成员。那很危险。以下划线开头的名称保留用于实现。 C 标准说:

ISO/IEC 9899:2011 §7.1.3 保留标识符

——以下划线和大写字母或其他字母开头的所有标识符 下划线始终保留用于任何用途。
— 所有以下划线开头的标识符始终保留用作标识符 在普通名称空间和标记名称空间中都具有文件范围。

这意味着如果你遇到问题,那么问题是你的,而不是系统的。显然,您的代码可以正常工作,但您应该意识到可能遇到的问题,并且避免它们是最明智的。


示例代码

这是一个基于上面代码的固定SSCCE:

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

typedef struct Command Command;
struct Command
{
    int _fd_out;
    int _fd_in;
    int _flag_pipe_in;
    int _flag_pipe_out;
    char **_commands;
};

typedef int Pipe[2];

enum { STDIN = STDIN_FILENO, STDOUT = STDOUT_FILENO, STDERR = STDERR_FILENO };

int main(void)
{
    char *ls_cmd[] = { "ls", 0 };
    char *grep_cmd[] = { "grep", "r", 0 };
    Command commands[] =
    {
        {
            ._fd_in  = 0, ._flag_pipe_in  = 0,
            ._fd_out = 1, ._flag_pipe_out = 1,
            ._commands = ls_cmd,
        },
        {
            ._fd_in  = 0, ._flag_pipe_in  = 1,
            ._fd_out = 1, ._flag_pipe_out = 0,
            ._commands = grep_cmd,
        }
    };
    int commands_num = sizeof(commands) / sizeof(commands[0]);

    /* Allow valgrind to check memory */
    Command *pcommands = malloc(commands_num * sizeof(Command));
    for (int i = 0; i < commands_num; i++)
        pcommands[i] = commands[i];

    for (int i = 0; i < commands_num; i++) {   //exec all the commands instants
        if (pcommands[i]._flag_pipe_out == 1) { //creates pipe if necessary
            Pipe pipe_fd;
            if (pipe(pipe_fd) == -1) {
                perror("Error: \"pipe()\" failed");
            }
            pcommands[i]._fd_out = pipe_fd[1];
            pcommands[i+1]._fd_in = pipe_fd[0];
        }
        pid_t pid = fork();   //the child exec the commands
        if (pid == -1) {
            perror("Error: \"fork()\" failed");
            break;
        } else if (!pid) { //child process

            if (pcommands[i]._flag_pipe_in == 1) {  //if there was a pipe to this command
                assert(i > 0);
                assert(pcommands[i-1]._flag_pipe_out == 1);
                assert(pcommands[i-1]._fd_out > STDERR);
                if (dup2(pcommands[i]._fd_in, STDIN) == -1) {
                    perror("Error: \"dup2()\" failed");
                    exit(0);
                }
                close(pcommands[i]._fd_in);
                close(pcommands[i-1]._fd_out);
            }

            if (pcommands[i]._flag_pipe_out == 1) { //if there was a pipe from this command
                assert(i < commands_num - 1);
                assert(pcommands[i+1]._flag_pipe_in == 1);
                assert(pcommands[i+1]._fd_in > STDERR);
                if (dup2(pcommands[i]._fd_out, STDOUT) == -1) {
                    perror("Error: \"dup2()\" failed");
                    exit(0);
                }
                close(pcommands[i]._fd_out);
                close(pcommands[i+1]._fd_in);
            }
            execvp(pcommands[i]._commands[0] , pcommands[i]._commands); //run the command

            perror("Error: \"execvp()\" failed");
            exit(1);
        }
        else
            printf("Child PID %d running\n", (int)pid);
    }

    //closing all the open pipe fd's
    for (int i = 0; i < commands_num; i++) {
        if (pcommands[i]._fd_in != STDIN) { //if there was another stdin that is not 0
            close(pcommands[i]._fd_in);
        }
        if (pcommands[i]._fd_out != STDOUT) { //if there was another stdout that is not 1
            close(pcommands[i]._fd_out);
        }
    }

    int status;
    pid_t corpse;
    while ((corpse = waitpid(-1, &status, 0)) > 0)
        printf("Child PID %d died with status 0x%.4X\n", (int)corpse, status);

    free(pcommands);

    return(0);
}

据我所知,你会怎么做,才不会变得“无可争辩地混乱”?

我可能会保留管道信息,这样孩子就不必担心断言中包含的条件(在管道中访问孩子之前或之后的孩子信息)。如果每个孩子只需要访问自己的数据结构中的信息,那就更干净了。我将重新组织“结构命令”,使其包含两个管道,以及哪个管道包含需要关闭的信息的指示符。在许多方面,与您所拥有的并没有根本不同;只是那个孩子更整洁i只需要看pcommands[i]

您可以在 C Minishell adding pipelines 看到不同上下文中的部分答案。

【讨论】:

  • 谢谢,我明天试试这个。我认为,通过快速阅读,问题在于我没有在 fork 之后立即关闭父亲的 fd...(我之前尝试过,但发生了一些错误...我会再试一次)
  • 生活比这复杂一点。在子进程中,您也必须关闭管道的两端,并且考虑到您组织数据结构的方式,这意味着子 i 必须查看子 的数据结构i>i-1 如果它的输入是一个管道(关闭管道的输出端)并且在子 i+1 的数据结构中如果它的输出是一个管道(到关闭管道的输入端),这无疑是混乱的。您还需要关闭主外壳中的管道。我将您的代码反向工程为 SSCCE (Short, Self-Contained, Correct Example)。
  • 好的,谢谢,我还没试过,我稍后再做......我只是想问一下SSCCE这个东西是什么?你做过这个改变吗?还是要我做?这部分我没看懂,因为我没看到代码有什么变化……
  • 我的机器上的代码作为 SSCCE 可用;我还没有把它上传到这里(我有“损坏”的代码——可编译但不像上面的片段那样工作;我也有“固定”的代码——有必要的更改)。
  • 谢谢,成功了!就我所知,你应该怎么做,所以它不会变得“无可争辩地混乱”?我会很感激你的回答!
猜你喜欢
  • 2020-07-15
  • 1970-01-01
  • 2013-10-20
  • 2021-07-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-08-11
  • 1970-01-01
相关资源
最近更新 更多