一、文件描述符

在linux和unix系统中,一切都是文件,内核是通过文件描述符来访问文件,文件描述符是非负整数,最大值受系统最大可打开的文件数限制。可以使用命令查看:

ulimit -a

查看open files的值,默认是1024。

二、标准流
先来看看维基百科中标准流的定义: 在linux和unix系统中,一个程序运行时和环境交互(INPUT/OUTPUT)的通道,叫标准流。

对于Linux下的进程,每个进程都有三个标准的文件描述符,对应于三个标准流:

  1. 标准输入:文件描述符为 0,对应标准流 stdin ,对应设备为键盘
  2. 标准输出:文件描述符为 1,对应标准流 stdout,对应设备为显示器
  3. 标准错误:文件描述符为 2,对应标准流 stderr,对应设备为显示器

linux下的用户都是通过终端进行交互,如bash、sh、zsh等,从键盘接收输入,在显示器上打印输出

三、linux I/O重定向分类

Linux重定向操作发生在三个标准流之间,主要来完成复杂的任务,允许你控制程序或命令的输入输出流。
主要分为:

  1. 输入重定向 <
  2. 输入重定向读写模式 <> ,以读写模式重定向到输入,没有则创建文件
  3. 输出重定向 >
  4. 输出重定向追加模式 >>
  5. 非标准重定向 &> ,将标准输出和标准错误重定向
  6. 非标准重定向追加模式 &>>
  7. 管道 | ,将一个命令的输出做为另外一个命令的输入

四、举例

1.正常执行命令,将标准输出和标准错误都显示在终端上,我相信能看到我这篇文章的人都会

./command

2.将标准输出重定向到文件

./command 1> stdout.log 

1在终端中可以省略,就变成了最常见的情况:

./command > stdout.log

此时如果重新执行有错误,标准错误会显示在终端上

3.将标准错误重定向到文件

./command 2> stderr.log

此时标准输出会正常打印到终端上

4.执行命令,将输出日志打印到stdout.log,将错误日志打印到stderr.log

./command 1>stdout.log 2>stderr.log

此时终端什么都不显示

5.执行命令,将标准输出和标准错误都重定向到同一个文件

./command &> all.log

或者用麻烦一点的方法:

./command > all.log 2>&1

6.执行命令,即重定向标准输出又同时在终端打印,需要借助管道符和命令tee

./command |tee  stdout.log

将标准输出和标准错误全部重定向,并输出到终端

./command 2>&1 |tee all.log

还记得上面说的管道的定义吧,这个实际上是执行两个命令,./command 2>&1 将全部日志重定向到标准输出,在作为标准输入传给tee命令

7.执行命令,打印标准输出,重定向标准输出和标准错误

./command 2>stderr.log |tee stdout.log

8.丢弃输出,/dev/null是系统的垃圾箱,可以将标准输出、标准错误全部输出到这个设备

./command >/dev/null
./command 2>/dev/null
./command &> /dev/null

有了上面的说明,我相信这三条命令不用解解释了

9.利用输出重定向创建文件

> newfile.txt

这个命令等同于:

: > newfile.txt

:是一个占位符, 不产生任何输出,这种情况可以用来创建空文件

10.输入重定向

cat 0<  /etc/issue

0也可以省略不写

cat <  /etc/issue

11.输入重定向到交互的shell

cat << EOF
Hi nixops.me
EOF

此时是以EOF为分隔符,可以输入任意内容,直到再次输入EOF完成,输入的内容会做为cat命令的输入显示,此例中输出Hi nixops.me

五、高级用法
上面讲到的例子只是在标准输入(0)、标准输出(1)和标准错误(2)之间进行IO重定向,开头也说了,文件描述符是非负整数,最大值受系统的ulimit里open files限制,除标准文件描述符外,那有没有办法在普通文件描述符之间重定向呢?
我们先来看一个刚刚举过的例子:

./command > all.log 2>&1

这个例子的意思是,将标准错误(2)重定向(>)到标准输出(1),并将所有标准输出重定向到all.log,那如果是普通的文件描述符,是否可以这样重定向呢?

答案是可以的。

例如,我有一个文件描述i和j,两个文件描述符之间进行重定向 i>&j,含义是:

  1. 重定向文件描述符i到j
  2. 指向i文件的所有输出都发送到j

同理,也可以将标准文件描述符,重定向到普通文件描述符,如 >&i,含义是:将标准输出重定向到文件描述符i。

下面我们来举例说明,我们先来创建一个文件描述符,可以用exec命令,以读写模式创建一个文件描述符:

exec 3<>newfile

然后向文件描述符3写入内容:

echo 'nixops.me' >&3

需要关闭文件描述符3,使用命令:

exec 3>&-

关闭后就生成了newfile文件,查看一下内容:

cat newfile
nixops.me

经过上面的例子,下面这些也很好理解,简单说明一下:

  1. >&- 等同于 1>&- 关闭标准输出
  2. <&- 等同于 0<&- 关闭标准输入
  3. >&i 等同于 1>&i 将标准输出重定向到文件描述符i
  4. <&i 等同于 0<&i 将文件描述符i重定向到标准输入
  5. i<&- 关闭文件描述符i的输入
  6. i>&- 关闭文件描述符i的输出
  7. i>&j- 将文件描述符j的输出文件移动到i上,并关闭j
  8. i<&j- 将文件描述符j的输入文件移动到i上,并关闭j
  9. 创建文件描述符时名称可以用变量

附:
askubuntu.com里有个答案下,有人做了一个表格,直观的看出在使用重定向的情况下,stdout、stderr在终端是否显示、是否重定向到文件、文件是否会创建。这几种情况一目了然,帮助理解。

终端显示 文件内容 文件状态
语法 StdOut StdErr StdOut StdErr File
command > 创建
command >> 追加
command 2> 创建
command 2>> 追加
command &> 创建
command &>> 追加
command | tee 创建
command | tee -a 追加
command |& tee 创建
command |& tee -a 追加

参考文章:
https://en.wikipedia.org/wiki/Standard_streams
https://askubuntu.com/questions/420981/how-do-i-save-terminal-output-to-a-file/731237#731237
https://www.putorius.net/linux-io-file-descriptors-and-redirection.html
https://www.junmajinlong.com/shell/fd_duplicate/