结合Linux文件描述符谈重定向,彻底理解重定向的本质!

Linux重定向》一节讲解了输入输出重定向的各种写法,并提到了文件描述符的概念;《Linux文件描述符》一节从底层剖析了文件描述符的本质,它只不过是一个数组下标。本节我们就将两者结合起来,看看 Shell 是如何借助文件描述符实现重定向的。

Linux 系统这个“傻帽”只有一根筋,每次读写文件的时候,都从文件描述符下手,通过文件描述符找到文件指针,然后进入打开文件表和 i-node 表,这两个表里面才真正保存了与打开文件相关的各种信息。

试想一下,如果我们改变了文件指针的指向,不就改变了文件描述符对应的真实文件吗?比如文件描述符 1 本来对应显示器,但是我们偷偷将文件指针指向了 log.txt 文件,那么文件描述符 1 也就和 log.txt 对应起来了。

文件指针只不过是一个内存地址,修改它是轻而易举的事情。文件指针是文件描述符和真实文件之间最关键的“纽带”,然而这条纽带却非常脆弱,很容易被修改。

Linux 系统提供的函数可以修改文件指针,比如 dup()、dup2();Shell 也能修改文件指针,输入输出重定向就是这么干的。

对,没错,输入输出重定向就是通过修改文件指针实现的!更准确地说,发生重定向时,Linux 会用文件描述符表(一个结构体数组)中的一个元素给另一个元素赋值,或者用一个结构体变量给数组元素赋值,整体上的资源开销相当低。

你看,发生重定向的时候,文件描述符并没有改变,改变的是文件描述符对应的文件指针。对于标准输出,Linux 系统始终向文件描述符 1 中输出内容,而不管它的文件指针指向哪里;只要我们修改了文件指针,就能向任意文件中输出内容。

以下面的语句为例来说明:

echo "www.xinbaoku.com" 1>log.txt

文件描述符表本质上是一个结构体数组,假设这个结构体的名字叫做 FD。发生重定向时,Linux 系统首先会打开 log.txt 文件,并把各种信息添加到 i-node 表和文件打开表,然后再创建一个 FD 变量(通过这个变量其实就能读写文件了),并用这个变量给下标为 1 的数组元素赋值,覆盖原来的内容,这样就改变了文件指针的指向,完成了重定向。

Shell 对文件描述符的操作

前面提到,>是输出重定向符号,<是输入重定向符号;更准确地说,它们应该叫做文件描述符操作符。> 和 < 通过修改文件描述符改变了文件指针的指向,所以能够实现重定向的功能。

除了 > 和 <,Shell 还是支持<>,它的效果是前面两者的总和。

Shell 文件描述符操作方法一览表
分类 用法 说明
输出 n>filename 以输出的方式打开文件 filename,并绑定到文件描述符 n。n 可以不写,默认为 1,也即标准输出文件。
n>&m 用文件描述符 m 修改文件描述符 n,或者说用文件描述符 m 的内容覆盖文件描述符 n,结果就是 n 和 m 都代表了同一个文件,因为 n 和 m 的文件指针都指向了同一个文件。

因为使用的是>,所以 n 和 m 只能用作命令的输出文件。n 可以不写,默认为 1。
n>&- 关闭文件描述符 n 及其代表的文件。n 可以不写,默认为 1。
&>filename 将正确输出结果和错误信息全部重定向到 filename。
输入 n<filename 以输入的方式打开文件 filename,并绑定到文件描述符 n。n 可以不写,默认为 0,也即标准输入文件。
n<&m 类似于 n>&m,但是因为使用的是<,所以 n 和 m 只能用作命令的输入文件。n 可以不写,默认为 0。
n<&- 关闭文件描述符 n 及其代表的文件。n 可以不写,默认为 0。
输入和输出 n<>filename 同时以输入和输出的方式打开文件 filename,并绑定到文件描述符 n,相当于 n>filename 和 n<filename 的总和。。n 可以不写,默认为 0。

【实例1】前面的文章中提到了下面这种用法:

command >file 2>&1

它省略了文件描述符 1,所以等价于:

command 1>file 2>&1

这个语句可以分成两步:先执行1>file,让文件描述符 1 指向 file;再执行2>&1,用文件描述符 1 修改文件描述符 2,让 2 和 1 的内容一样。最终 1 和 2 都指向了同一个文件,也就是 file。所以不管是向 1 还是向 2 中输出内容,最终都输出到 file 文件中。

这里需要注意执行顺序,多个操作符在一起会从左往右依次执行。对于上面的语句,就是先执行1>file,再执行2>&1;如果写作下面的形式,那就南辕北辙了:

command 2>&1 1>file

Shell 会先执行2>&1,这样 1 和 2 都指向了标准错误输出文件,也即显示器;接着执行1>file,这样 1 就指向了 file 文件,但是 2 依然指向显示器。最终的结果是,正确的输出结果输出到了 file 文件,错误信息却还是输出到显示器。

【实例2】一个比较奇葩的重定向写法。

echo "新宝库" 10>log.txt >&10

先执行10>log.txt,打开 log.txt,并给它分配文件描述符 10;接着执行>&10,用文件描述符 10 来修改文件描述符 1(对于>,省略不写的话默认为 1),让 1 和 10 都指向 log.txt 文件,最终的结果是向 log.txt 文件中输出内容。

这条语句其实等价于echo "新宝库" >log.txt,我之所以写得这么绕,是为了让大家理解各种操作符的用法。

文件描述符 10 只用了一次,我们在末尾最好将它关闭,这是一个好习惯。

echo "新宝库" 10>log.txt >&10 10>&-