【问题标题】:Pipes, dup2 and exec()管道、dup2 和 exec()
【发布时间】:2016-02-26 08:11:32
【问题描述】:

我必须编写一个可以运行管道的 shell。例如像ls -l | wc -l"这样的命令。我已经成功解析了用户给出的命令如下:

"ls" = firstcmd

"-l" = frsarg

"wc" = scmd

"-l" = secarg

现在我必须使用两个叉子,因为命令是两个和一个管道。 我为执行命令而编写的代码块如下:

pid_t pid;
int fd[2];

pipe(fd);
pid = fork();

if(pid==0)
{        
    dup2(fd[WRITE_END], STDOUT_FILENO);
    close(fd[READ_END]);
    execlp(firstcmd, firstcmd, frsarg, (char*) NULL);
}
else
{ 
    pid=fork();

    if(pid==0)
    {
        dup2(fd[READ_END], STDIN_FILENO);
        close(fd[WRITE_END]);
        execlp(scmd, scmd, secarg, (char*) NULL);
    }
}

因此,当我运行我的 shell 并输入命令 ls -l | wc -l(例如)时,execs 的结果不会显示,但 shell 会继续正常运行。

奇怪的是,命令的结果只有在我用“exit”或“^C”终止我的shell时才会显示。

该输出有什么问题?为什么我输入命令后没有立即显示?

【问题讨论】:

  • 需要关闭父进程中的管道FD。
  • 您能否编辑我的代码以帮助我理解您的意思? @Barmar
  • 经验法则:如果将管道的一端复制到标准输入或标准输出,则应在使用exec*() 函数之前关闭原始管道的两端(或以其他方式继续)。有例外;他们少之又少。很少(在 SO 和 IRL 上)您会遇到使用关闭太多描述符的管道的程序;找到一个没有关闭足够描述符的程序是很常见的。如果有多个孩子和/或多个管道来混淆事物,这种情况尤其常见。
  • 顺便说一下,如果您查看github.com/jleffler/soq/tree/master/src/so-4380-8114 中的代码,您会发现一个有趣的示例,其中“没有关闭足够多的文件描述符导致程序在足够大的输入上失败”。当输入很小时,它工作得很好。当输入足够大时,由于管道没有正确关闭,程序就会陷入僵局。 (不,干扰代码并不直接在 Stack Overflow 上。)

标签: c linux shell exec dup2


【解决方案1】:

嗯,够近了。你错过了在 fork 之后处理某些文件描述符的关闭。

这里有一些参考:

  1. 关于管道,http://unixwiz.net/techtips/remap-pipe-fds.html
  2. 关于文件描述符, http://www.usna.edu/Users/cs/aviv/classes/ic221/s14/lec/09/lec.html

这是我的代码:

#include  <fcntl.h>                              //
#include  <stdio.h>                              //
#include  <stdlib.h>                             //
#include  <string.h>                             //
#include  <sys/types.h>                          //
#include  <sys/wait.h>                           //
#include  <sys/stat.h>                           //
#include  <termios.h>                            //
#include  <unistd.h>                             //
                                                 //
#define INPUT_END 1                              // INPUT_END means where the pipe takes input
#define OUTPUT_END 0                             // OUTPUT_END means where the pipe produces output
                                                 //
int main(int argc, char* argv[])                 //
{                                                //
    pid_t pid1;                                  // [STDIN -> terminal_input, STDOUT -> terminal_output]                       (of the parent process)
    pid_t pid2;                                  //
    int fd[2];                                   //
                                                 //
    pipe(fd);                                    // [STDIN -> terminal_input, STDOUT -> terminal_output, fd[0] -> pipe_input, fd[1] -> pipe_output]
    pid1 = fork();                               //
                                                 //
    if(pid1==0)                                  //
    {                                            // I am going to be the wc process (i.e. taking input from the pipe)
        close(fd[INPUT_END]);                    // [STDIN -> terminal_input, STDOUT -> terminal_output, fd[1] -> pipe_output] (of the WC process)
        dup2(fd[OUTPUT_END], STDIN_FILENO);      // [STDIN -> pipe_output, STDOUT -> terminal_output, fd[1] -> pipe_output]    (of the WC process)
        close(fd[OUTPUT_END]);                   // [STDIN -> pipe_output, STDOUT -> terminal_output]                          (of the WC process)
        execlp("wc", "wc", "-l",(char*) NULL);   //
    }                                            //
    else                                         //
    {                                            //
        pid2=fork();                             //
                                                 //
        if(pid2==0)                              //
        {                                        // I am going to be the ls process (i.e. producing output to the pipe)
            close(fd[OUTPUT_END]);               // [STDIN -> terminal_input, STDOUT -> terminal_output, fd[0] -> pipe_input] (of the ls process)
            dup2(fd[INPUT_END], STDOUT_FILENO);  // [STDIN -> terminal_input, STDOUT -> pipe_input, fd[0] -> pipe_input]      (of the ls process)
            close(fd[INPUT_END]);                // [STDIN -> terminal_input, STDOUT -> pipe_input]                           (of the ls process)
            execlp("ls","ls","-l",(char*) NULL); //
        }                                        //
                                                 //
        close(fd[OUTPUT_END]);                   // [STDIN -> terminal_input, STDOUT -> terminal_output, fd[0] -> pipe_input] (of the parent process)
        close(fd[INPUT_END]);                    // [STDIN -> terminal_input, STDOUT -> terminal_output]                      (of the parent process)
        waitpid(-1, NULL, 0);                    // As the parent process - we wait for a process to die (-1) means I don't care which one - it could be either ls or wc
        waitpid(-1, NULL, 0);                    // As the parent process - we wait for the another process to die.
                                                 // At this point we can safely assume both process are completed
    }                                            //
}                                                //

【讨论】:

  • 您的代码中也存在问题。根据pipe 的文档,第一个 INPUT_END/OUTPUT_END 的值不正确。要解决此问题,您已互换了 wcls 命令。最后,你还没有关闭父进程中的描述符。
  • @user1969104 我运行了你的代码,它工作了,但我发现了另一个问题。为了您的信息,我的代码(太大而无法在此处编写)使用其他执行程序,以防用户仅提供 "ls""ls -l"" ls -l /bin"。通常具有最多两个参数的命令。当用户给出命令 "ls -l | wc -l" 一切正常时,结果会立即显示,但是在该命令之后我只尝试 "ls" 或任何其他没有管道的命令都不会发生任何事情。为什么会这样?
  • 如果没有看到您的代码,我不确定您的意思,但请确保管道重定向(调用 pipe、fork、dup2)仅在参数中给出第二个命令时发生。否则,如果你只是调用execlp("ls","ls","-l",(char*) NULL),没有任何理由没有输出。
  • 请注意,using namespace std; 是 C 语言中的语法错误,除非您在编译器命令行中定义了不寻常的宏集合(因为这些宏不会在系统头文件中定义)。请不要发布 C++ 代码作为 C 问题的答案。列表中还有多个未使用的标题;这不是很优雅。 &lt;string.h&gt;&lt;sys/wait.h&gt;&lt;sys/stat.h&gt;&lt;termios.h&gt; 未使用,现代 POSIX 不需要明确包含 &lt;sys/types.h&gt;。如果execlp() 返回,您至少应该退出;可以说,您也应该打印一条错误消息。
【解决方案2】:

您需要关闭父进程和子进程中的所有管道描述符(在子进程中复制后)。在您的代码中,主要问题是,wc 进程不会退出,因为仍然存在写入者(因为父进程尚未关闭写入端)。如下所示的变化。我还在父进程中添加了waitpid,等待wc进程。

pid_t pid;
int fd[2];

pipe(fd);
pid = fork();

if(pid==0)
{
    dup2(fd[WRITE_END], STDOUT_FILENO);
    close(fd[READ_END]);
    close(fd[WRITE_END]);
    execlp(firstcmd, firstcmd, frsarg, (char*) NULL);
    fprintf(stderr, "Failed to execute '%s'\n", firstcmd);
    exit(1);
}
else
{ 
    pid=fork();

    if(pid==0)
    {
        dup2(fd[READ_END], STDIN_FILENO);
        close(fd[WRITE_END]);
        close(fd[READ_END]);
        execlp(scmd, scmd, secarg,(char*) NULL);
        fprintf(stderr, "Failed to execute '%s'\n", scmd);
        exit(1);
    }
    else
    {
        int status;
        close(fd[READ_END]);
        close(fd[WRITE_END]);
        waitpid(pid, &status, 0);
    }
}

【讨论】:

  • 哦,是的。这是我见过的最正确的答案。它完美地工作。太感谢了。我坚持了几个小时。 @user1969104
【解决方案3】:

嗯,对于这样的事情,一个简单而有效的工作就是用管道制作一个脚本,然后用你的 C 代码中的一些 exec 命令调用该脚本。

脚本.sh

#!/bin/sh
ls -l | wc -l

而你只是在你的 C 程序中做这样的事情

char *argv[] = {"script.sh", NULL};
execv(argv[0], argv);

请注意,您必须将 script.sh 放在 C 程序的同一目录中。

【讨论】:

  • 是的,写一个shell来执行那个管道脚本,然后让shell调用自己来执行一个管道脚本。看看您需要多长时间才能用完资源。
猜你喜欢
  • 2015-07-03
  • 2011-06-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多