linux文件描述符和IO重定向
一、文件描述符
在linux和unix系统中,一切都是文件,内核是通过文件描述符来访问文件,文件描述符是非负整数,最大值受系统最大可打开的文件数限制。可以使用命令查看:
ulimit -a
查看open files的值,默认是1024。
二、标准流
先来看看维基百科中标准流的定义: 在linux和unix系统中,一个程序运行时和环境交互(INPUT/OUTPUT)的通道,叫标准流。
对于Linux下的进程,每个进程都有三个标准的文件描述符,对应于三个标准流:
- 标准输入:文件描述符为 0,对应标准流 stdin ,对应设备为键盘
- 标准输出:文件描述符为 1,对应标准流 stdout,对应设备为显示器
- 标准错误:文件描述符为 2,对应标准流 stderr,对应设备为显示器
linux下的用户都是通过终端进行交互,如bash、sh、zsh等,从键盘接收输入,在显示器上打印输出
三、linux I/O重定向分类
Linux重定向操作发生在三个标准流之间,主要来完成复杂的任务,允许你控制程序或命令的输入输出流。
主要分为:
- 输入重定向 <
- 输入重定向读写模式 <> ,以读写模式重定向到输入,没有则创建文件
- 输出重定向 >
- 输出重定向追加模式 >>
- 非标准重定向 &> ,将标准输出和标准错误重定向
- 非标准重定向追加模式 &>>
- 管道 | ,将一个命令的输出做为另外一个命令的输入
四、举例
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,含义是:
- 重定向文件描述符i到j
- 指向i文件的所有输出都发送到j
同理,也可以将标准文件描述符,重定向到普通文件描述符,如 >&i,含义是:将标准输出重定向到文件描述符i。
下面我们来举例说明,我们先来创建一个文件描述符,可以用exec命令,以读写模式创建一个文件描述符:
exec 3<>newfile
然后向文件描述符3写入内容:
echo 'nixops.me' >&3
需要关闭文件描述符3,使用命令:
exec 3>&-
关闭后就生成了newfile文件,查看一下内容:
cat newfile
nixops.me
经过上面的例子,下面这些也很好理解,简单说明一下:
- \>&- 等同于 1>&- 关闭标准输出
- <&- 等同于 0<&- 关闭标准输入
- \>&i 等同于 1>&i 将标准输出重定向到文件描述符i
- <&i 等同于 0<&i 将文件描述符i重定向到标准输入
- i<&- 关闭文件描述符i的输入
- i>&- 关闭文件描述符i的输出
- i>&j- 将文件描述符j的输出文件移动到i上,并关闭j
- i<&j- 将文件描述符j的输入文件移动到i上,并关闭j
- 创建文件描述符时名称可以用变量
附:
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/