使用Bash编程时,重定向是最常用到的技术之一。标准输出重定向还比较直观一个‘>'就搞定了,但是多个输出重定向时就有点乱了,而且输入重定向理解不顺,使用不自然。理解kernel如何管理文件描述符后,使用起来就很自然了,原理也比较简单!
Linux对于文件描述符的管理主要是通过三个表实现的:file descriptor table、file table、inode table。其关系如下图所示:
上图中显示,每个进程都有一个独立的fd table,fd table记录了fd与file实例的映射关系,file实例最终指向inode,inode实例保存文件的硬件和软件的相关信息。那么这个三个表格具体负责什么功能呢,每个表格需要包含什么信息呢?比如使用open(path, "w")打开一个文件时, “w“权限保存在哪呢?一个文件的duxie可执行权限又保存在哪呢?
file descriptor table:在内核中,每个进程对应一个task_struct结构实例,该实例有一个成员变量files(files_struct指针)。指针files指向一个files_struct实例,file_struct实例用一个成员变量fdtab(fdtable实例)。fdtab就是该进程的file descriptor table(一个fdtable结构)。
- fdtable.openfds。fdtable有一个成员变量open_fds,它指向一个ulong型数组,该数组其实是一个位图,用来记录所有已打开的文件描述符。如:打开文件/tmp/test.txt,且返回的文件描述符为5,那么openfds指向数组的第五位将被置1。给打开文件分配描述符时也是从该位图中查找最小为0的位,置1后返回的该位的index即为文件描述符。
- fdtable.fd。fdtable有一个成员变量fd,它是一个指向file指针数组的指针,索引即为文件描述符。这样通过fdtab可以管理进程的文件描述符,并可通过文件描述符找到打开文件对应的file实例。
file table:它是一个由file实例组成的双向链表。每一次调用open打开一个文件时都会新建一个file实例,并加入file table,所以对一个file调用多次open的话,那么会有多个file实例对应该文件。
- file.f_mode。file结构中成员变量f_mode记录了open()打开文件时指定的权限,r、w、a等。
- file.f_ra。f_ra是file_ra_state实例,它记录当前文件的读取位置,下次读写,将从此处开始,lseek()函数应该改变就是此值吧!
- f_inode。指向该文件对应的inode实例。
inode table。inode实例的双链表结构。inode是包含文件固有信息的结构,创建、修改、访问时间、文件所有者及可执行权限等等,stat()函数获取的信息基本都来自inode结构。
在新建每一个进程时,系统都会默认打开三个文件stdin、stdout、stderr,对应的文件描述符分别为1、2、3。这也就解释了为什么进程不用打开文件,就能向标准输入输出,获取和打印信息了。
- ’1>test.txt‘ 将标准输出重定向到test.txt,即以w方式打开test.txt,并将描述符1映射到test.txt的file实例。没有指定左边参数,默认为1;
- ‘1>>test.txt' 与‘>'相同,区别在于以a方式打开文件test.txt,以追加的方式写入文件,而不是覆盖。没有指定左边参数,默认为1;
- ‘0<test.txt' 将标准输入重定向到test.txt。以r方式打开文件test.txt,将文件描述符0与打开文件生成的file实例映射。没有指定左边参数,默认为0;
- ’exec 4<>test.txt' 以可读写方式打开文件test.txt,并指定文件描述符为4;
- '1>&4' 修改文件描述符1指向的file实例为文件描述符4指向的file实例。
example:
$ls 1>test.txt
将标准输出重定向到txt文件
$ls 2>&1
按上面解释来分析下:
$ls 1>/dev/null 2>&1
#目标清空输出
1->null;2->null ==> (1, 2)->null
也就很好的解释了,為什麼 ls 1>/dev/null2>&1要按这个顺序写。这样你也可以预言下:ls 2>&1 1>/dev/null的结果了。
参考文献:
- http://tldp.org/LDP/abs/html/io-redirection.html
- http://www.usna.edu/Users/cs/aviv/classes/ic221/s14/lec/18/lec.html