我正在用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]);
如果我能得到一些指针,我将非常感激。我不确定我的代码的哪一部分,我应该实现重定向。 在此先感谢
答案 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
的结果。