dup dup2与重定向(总结)

dup和dup2函数

#include <unistd.h>

int dup(int filedes);

int dup2(int filedes,int filedes2); //两函数的返回值:若成功则返回新的文件描述符,若出错则返回-1

 

由dup返回的新文件描述符一定是当前可用文件描述符中的最小数值。用dup2则可以用filedes2参数指定新描述符的数值。

如果filedes2已经打开,则先将其关闭。如果filedes等于filedes2,则dup2返回filedes2,则不关闭它。

 

一、重定向问题:

下面是一个例子程序:

#define TESTSTR "Hello dup2\n"

int main() {

        int     fd3;

        fd3 = open("testdup2.dat", 0666);

        if (fd < 0) {

                printf("open error\n");

                exit(-1);

        }

        if (dup2(fd3, STDOUT_FILENO) < 0) {       

                printf("err in dup2\n");

        }

        printf(TESTSTR);

        return 0;

}

其结果就是你在testdup2.dat中看到"Hello dup2"。

 

二、重定向后恢复

CU上有这样一个帖子,就是如何在重定向后再恢复原来的状态?首先大家都能想到要保存重定向前的文件描述符。那么如何来保存呢,象下面这样行么?

int s_fd = STDOUT_FILENO;

int n_fd = dup2(fd3, STDOUT_FILENO);

还是这样可以呢?

int s_fd = dup(STDOUT_FILENO);

int n_fd = dup2(fd3, STDOUT_FILENO);

这 两种方法的区别到底在哪呢?答案是第二种方案才是正确的,分析如下:按照第一种方法,我们仅仅在"表面上"保存了相当于fd_t(按照我前面说的理解方 法)中的index,而在调用dup2之后,ptr所指向的文件表项由于计数值已为零而被关闭了,我们如果再调用dup2(s_fd, fd3)就会出错(出错原因上面有解释)。而第二种方法我们首先做一下复制,复制后的状态如下图所示:

进程A的文件描述符表(after dup)

   ------------

fd0 0   | p0

   ------------

fd1 1   | p1 -------------> 文件表1 ---------> vnode1

   ------------                 /|

fd2 2   | p2                /

   ------------             /

fd3 3   | p3 -------------> 文件表2 ---------> vnode2

   ------------          /

s_fd 4   | p4 ------/

   ------------

... ...

... ...

   ------------

调用dup2后状态为:

进程A的文件描述符表(after dup2)

   ------------

fd0 0   | p0

   ------------

n_fd 1   | p1 ------------

   ------------               \

fd2 2   | p2                 \

   ------------                _\|

fd3 3   | p3 -------------> 文件表2 ---------> vnode2

   ------------

s_fd 4   | p4 ------------->文件表1 ---------> vnode1

   ------------

... ...

... ...

   ------------

dup(fd)的语意是返回的新的文件描述符与fd共享一个文件表项。就如after dup图中的s_fd和fd1共享文件表1一样。

确定第二个方案后重定向后的恢复就很容易了,只需调用dup2(s_fd, n_fd);即可。下面是一个完整的例子程序:

#define TESTSTR "Hello dup2\n"

#define SIZEOFTESTSTR 11

int main() {

        int     fd3;

        int     s_fd;

        int     n_fd;

        fd3 = open("testdup2.dat", 0666);

        if (fd3 < 0) {

                printf("open error\n");

                exit(-1);

        }

        /* 复制标准输出描述符 */

        s_fd = dup(STDOUT_FILENO);

        if (s_fd < 0) {

                printf("err in dup\n");

        }

        /* 重定向标准输出到文件 */

        n_fd = dup2(fd3, STDOUT_FILENO);

        if (n_fd < 0) {

                printf("err in dup2\n");

        }

        write(STDOUT_FILENO, TESTSTR, SIZEOFTESTSTR);   /* 写入testdup2.dat中 */

        /* 重定向恢复标准输出 */

        if (dup2(s_fd, n_fd) < 0) {

                printf("err in dup2\n");

        }

        write(STDOUT_FILENO, TESTSTR, SIZEOFTESTSTR); /* 输出到屏幕上 */

        return 0;

}

注 意这里我在输出数据的时候我是用了不带缓冲的write库函数,如果使用带缓冲区的printf,则最终结果为屏幕上输出两行"Hello dup2",而文件testdup2.dat中为空,原因就是缓冲区作怪,由于最终的目标是屏幕,所以程序最后将缓冲区的内容都输出到屏幕。

 

三、父子进程间的dup/dup2

由fork调用得到的子进程和父进程的相同文件描述符共享同一文件表项,如下图所示:

父进程A的文件描述符表

   ------------

fd0 0   | p0

   ------------

fd1 1   | p1 -------------> 文件表1 ---------> vnode1

   ------------                            /|\

fd2 2   | p2                             |

   ------------                            |

                                               |

子进程B的文件描述符表                |

   ------------                             |

fd0 0   | p0                             |

   ------------                             |

fd1 1   | p1 ---------------------|

   ------------

fd2 2   | p2

   ------------

所以恰当的利用dup2和dup可以在父子进程之间建立一条“沟通的桥梁”。这里不详述。

 

例子:

dup2(fd,0);

dup2(fd,1);

dup2(fd,2);

if(fd>2)

  close(fd);

 

答:如果fd是1,执行dup2(fd,1)后返回1,但是没有关闭描述符1.调用3次dup2后,3个描述符指向相同的文件表项,所以不需要关闭描述符

   如果fd是3,调用3此dup2后,有4个描述符指向相同的文件表项,这时就需要关闭描述符3.

 

重定向中注意:

注意2>&1 >outfile 与 >outfile 2>&1  

  qun@ThinkPad ~/tmp $ ./a.out > outfile 2>&1

  qun@ThinkPad ~/tmp $ cat outfile

  output to stderr

  output to stdin

  qun@ThinkPad ~/tmp $ ./a.out 2>&1 > outfile

  output to stderr

  

原因是:

  由于bash从左往右处理,在./a.out > outfile的时候,将a.out的fd 1定向到了outfile文件的fd上,然后才遇到2>&1,这时再将a.out的文件描述符2定向到文件描述符1上,这样stderr和stdout,就都到outfile上了。

而下面一个则不然,先遇到2>&1,这时将a.out的文件描述符2定向到文件描述符1上,1是终端。再遇到 > outfile,这时将a.out的文件描述符1到outfile这个文件。

用strace可以看到: 
1. command > file 2>&1 
这个命令中实现重定向的关键系统调用序列是: 
open(file) == 3 
dup2(3,1) 
dup2(1,2) 

2. command 2>&1 >file 
这个命令中实现重定向的关键系统调用序列是: 
dup2(1,2) 
open(file) == 3 
dup2(3,1) 

                                                                               

                                                                                                                                                     ————————————收集总结

posted @ 2012-04-21 16:56  yarpee  阅读(805)  评论(0编辑  收藏  举报