试图用“execvp()”运行“ls | grep r”

时间:2012-12-03 23:09:53

标签: c

  1. 我在两个子进程之间创建了pipe, 首先,我运行ls,写入正确的fd, 然后,我运行grep r,它从正确的fd读取,

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

  3. 问题在于grep没有退出,即使ls不再运行,它也会停留在那里

  4. 对于其他程序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);            
        }   
    }
    

    所以,我有一个&#34;命令&#34;即时pcommands[i] 它有: pipein,pipeout的旗帜 fdin,fdout, 和一个char **(对于真正的命令,比如&#34; ls -l&#34;)

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

    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 child:dup2到stdout execvp

    第二次: 不会创建管道 做叉子 儿童: pcomands [1]有pipein == 1 然后:dup2到输入 exevp ..

    此命令有效,我的输出是:

      

    errors.log exer2.pdf multipal_try

    (&#39; r&#39;所有的事情) 但后来它被卡住了,并没有离开grep .. 在另一个终端,我可以看到grep仍在工作

    我希望我关闭所有必须关闭的fd ...

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

    有人可以帮忙吗?感谢

1 个答案:

答案 0 :(得分:2)

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

经验法则:

  • 如果使用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);
}
  

据我所知,你会怎么做,所以它不会“无可争议地凌乱”?

我可能会保留管道信息,以便孩子不需要担心断言中包含的条件(在管道中之前或之后访问子节点的子信息)。如果每个孩子只需要访问自己数据结构中的信息,那就更清晰了。我重新组织'struct Command',因此它包含两个管道,以及哪个管道包含需要关闭的信息的指示器。在许多方面,与你所得到的并没有根本的不同;只是在那个孩子 i 中更整洁,只需要看pcommands[i]

您可以在C Minishell adding pipelines的不同背景下看到部分答案。