在C中一起实现管道和重定向

时间:2018-05-30 17:52:25

标签: c pipe fork

我正在用C创建一个最小的shell。我理解管道和重定向是如何工作的。我有两个程序,一个处理重定向的程序,例如ls -l > outputfile。我有另一个程序来处理一个管道,例如,ls - l | wc。我遇到的主要问题是把它们放在一起。基本上,我希望能够在一个程序中实现管道重定向。下面是管道代码的一部分。

   pipe(p);
      if (fork() == 0) {
      dup2(p[1], 1);
      close(p[0]);
      execvp(upstream[0], upstream );
      exit(1);
    }
    if (fork() == 0) {
    dup2(p[0], 0);

    close(p[1]);
    execvp(downstream[0], downstream);
    exit(1);
    }
    else { /* Parent */
        /*  so Close both ends of pipe */
    close(p[0]);
    close(p[1]);

如果我能得到一些指针,我将非常感激。我不确定我的代码的哪一部分,我应该实现重定向。 在此先感谢

2 个答案:

答案 0 :(得分:3)

因此,对于重定向方面,您需要在文件句柄的fork中使用dup2()。要设置管道,您需要在主线程中使用pipe()来创建句柄,然后在fork中使用dup2()。

请注意,您必须非常小心,pipe()不会为您提供低编号的文件句柄。您可以使用fcntrl(F_DUPFD,...)将它们欺负为高数字。此外,使用CLOEXEC摆脱浮动的所有垃圾二级手柄。

伪代码:

fin = open("input", read)
pipeIO = pipe()
fout = open("output", write)

fin2 = fcntrl(F_DUPFD_CLOEXEC, fin, 3);   close fin
pipeI2 = fcntrl(F_DUPFD_CLOEXEC, pipeI, 3);   close pipeI
pipeO2 = fcntrl(F_DUPFD_CLOEXEC, pipeO, 3);   close pipeO
fout2 = fcntrl(F_DUPFD_CLOEXEC, fout, 3);   close fout

fork 
{
   dup(fin2, 0)
   dup(pipeO2, 1)
  exec Command1
}
close fin2
close pipeO2
fork 
{
   dup(pipeI2, 0)
   dup(out2, 1)
   exec Command2
}
close fout2
close pipeI2

wait

或者,第二个分叉可以链式执行:

伪代码:

fin = open("input", read)
pipeIO = pipe()
fout = open("output", write)

fin2 = fcntrl(F_DUPFD_CLOEXEC, fin, 3);   close fin
pipeI2 = fcntrl(F_DUPFD_CLOEXEC, pipeI, 3);   close pipeI
pipeO2 = fcntrl(F_DUPFD_CLOEXEC, pipeO, 3);   close pipeO
fout2 = fcntrl(F_DUPFD_CLOEXEC, fout, 3);   close fout

fork 
{
   dup(fin2, 0)
   dup(pipeO2, 1)
  exec Command1
}
dup(pipeI2, 0)
dup(out2, 1)
exec Command2

您显然已经意识到,当您进行分叉时,子进程会有效地获取所有句柄重复,并且在代码实际执行时,只能打开一个版本。父或子必须关闭它不感兴趣的每个句柄。通过使用CLOEXEC我们减少了这种需要,但是如果你想将父进程保持为监视器,那么它需要关闭所有这些句柄的所有副本

复制功能的快速概述:

0 =管道(手柄[2])
这会创建一对手柄,它们作为输入和输出相互连接。经典地,您可以通过一个进程与fork进行通信,写入fork的一个句柄,另一个进程从另一个句柄读出,无论您想要使用它们的哪一种方式。在这种情况下,我们使用管道在两个子进程之间进行通信,并且不涉及父进程。

handle = dup(句柄)
这会在最低的未使用插槽中创建重复句柄。如果你的程序已关闭stdin / stdout / stderr,它将重用那些,这将是有问题的。这些句柄也会在exec调用中保持活动状态。

handle = dup2(handle,trghandle)
这会在您指定的索引处创建重复句柄。它会默默地关闭trghandle的任何现有用途,这既有用又烦人!

handle = dup3(handle,trghandle,flags)
这会在您指定的索引处创建重复句柄。它将默默关闭trghandle的任何现有用途,这既有用又烦人!您可以指定CLOEXEC等标志,它会在您调用exec时自动关闭句柄。 CLOEXEC在避免显式关闭文件方面非常有用。

int fcntl(int fd,int cmd,... / * arg * /);
 F_DUPFD(int)使用大于或等于arg的编号最小的可用文件描述符复制文件描述符fd。 F_DUPFD_CLOEXEC(int;自Linux 2.6.24起)对于F_DUPFD,另外还为重复文件描述符设置了close-on-exec标志。
...这使得很容易避免stdin / stdout。

答案 1 :(得分:0)

您想要执行

ls -l   |   wc   >   outputfile
 |           |         |
p-1        p-2      redirect the o/p of ls | wc to outputfile

要实现上述目标,您需要使用fork()系统调用& amp;创建流程(p-1和p-2)。然后使用dup()dup2()系统调用将o / p重定向到预期的位置。

这是示例代码

int main(int argc,char *argv[]) {
        int fd[2];
        pid_t cpid;
        close(1); /* so that output file should get file descriptor 1 */
        int file_dsptr = open(argv[3],O_CREAT|O_TRUNC |O_WRONLY , 0664); /* file should be exist in current directory, otherwise use O_CREAT | O_TRUNC */
        printf("[%d]\n",file_dsptr);

        if(argc == 4){
                if(pipe(fd) == -1){
                        perror("error in pipe creation");
                        exit(EXIT_FAILURE);
                }
                cpid = fork();
                if( cpid == 0 ){ /*child process */
                        close( fd[0] ); /* close read end of pipe */
                        if( -1 == dup2(fd[1] , file_dsptr)){ /* duplicate fd[1] to fd where data.txt points */
                                exit( EXIT_FAILURE );
                        }
                        if(-1 == execlp(argv[1] , argv[1] , NULL )){ /* executes the commands */
                                exit( EXIT_FAILURE );
                        }
                        close( fd[1] );
                        _exit( EXIT_SUCCESS );
                }
                else if(cpid > 0){
                        wait( NULL ); /* wait for child to completes */
                        close( fd[1] ); /* close write end of pipe */
                        if( -1 == dup2(fd[0] , STDIN_FILENO)) {
                                exit( EXIT_FAILURE );
                        }
                        if(-1 == execlp(argv[2] , argv[2], NULL )) {
                                exit( EXIT_FAILURE );
                        }
                        close( fd[0] );
                        _exit( EXIT_SUCCESS );
                }
                else{
                        fprintf(stderr, "fork failed\n");
                        exit(EXIT_FAILURE);
                }
        }
        else{
                fprintf(stderr, "Usage Msg :. ./a.out ls wc data.txt \n");
                exit(EXIT_FAILURE);
        }
        return 0;
}

正如我在评论中提到的那样运行上面的代码

gcc -Wall test.c
./a.out ls  wc  outputfile

现在检查outputfile的内容,它应该是ls | wc的结果。