当在 Linux 中执行一条命令时,你输入的文字、命令输出的结果、以及报错信息,都是通过一组程序启动前就准备好的连接来传输的。这些连接就是标准输入(standard input)、标准输出(standard output)和标准错误(standard error),通常简称为 stdin、stdout 和 stderr。
Shell 可以在不修改程序本身的情况下,将这些数据流连接到终端、文件、管道或其他目标。本文将介绍这三个标准流的作用、为什么它们对应的文件描述符是 0、1 和 2,以及如何使用 shell 重定向来改变它们的去向。
1. 三个标准流
一个进程在启动时,通常会从父进程继承三个已打开的文件描述符。当你在交互式 shell 中执行命令时,shell 默认会将这三个描述符都连接到当前终端:
把 stdin 简单理解为“键盘”是一种方便的说法,但实际上程序是从终端设备读取数据的。终端负责处理键盘输入并将其传递给程序。在脚本或管道中,stdin 也可能来自文件或其他命令。
文件描述符的数字很重要,因为 shell 的重定向语法就用到了它们。stdout 和 stderr 默认都会显示在终端上,所以它们看起来像是一个数据流,但实际上是相互独立的。脚本可以将正常结果保存到一个文件中,同时将错误信息发送到其他地方。
2. 分别查看 stdout 和 stderr
下面两条命令分别输出一条普通信息和一条警告信息:
printf'%s\n'"backup complete"
printf'%s\n'"warning: archive not found" >&2
输出结果:
backup complete
warning: archive not found
两行内容都显示在同一个终端上,但它们走的是不同的数据流。第一条 printf 写入的是 stdout。第二条命令中的 >&2 重定向将其输出发送到了 stderr,这样脚本就可以报告诊断信息,而不会把这些信息混入其他命令需要处理的数据中。
3. 改变数据流的去向
Shell 在启动命令之前就会设置好重定向。程序本身并不需要关心这些文件描述符指向的是终端、文件还是管道,它只是照常读写。
> 操作符将 stdout 重定向到文件,如果文件不存在则创建,存在则覆盖其内容:
ls /etc > files.txt
终端上不会显示任何内容,因为目录列表已经被写入了 files.txt。由于没有指定描述符编号时 > 默认操作的是 stdout,所以 > files.txt 和 1> files.txt 的效果是一样的。
stderr 默认仍然连接到终端,除非你对描述符 2 也做了重定向。下面这条命令同时访问一个存在的路径和一个不存在的路径:
ls /etc /nonexistent > files.txt
输出结果:
ls: cannot access '/nonexistent': No such file or directory
成功的目录列表被写入了 files.txt,而错误信息仍然显示在终端上,因为它走的是 stderr。要单独捕获错误信息,可以重定向描述符 2:
ls /etc /nonexistent > files.txt 2> errors.txt
现在,正常结果保存在 files.txt 中,诊断信息保存在 errors.txt 中。
如果想把两个数据流都保存到同一个文件中,可以使用可移植的 shell 语法:先重定向 stdout,再将 stderr 复制到 stdout 的目标位置:
command > output.log 2>&1
这里的 2>&1 表示让文件描述符 2 指向文件描述符 1 当前的目标位置。重定向是从左到右依次处理的,所以调换顺序会产生不同的结果。关于如何合并、追加、管道传递和丢弃输出,可以参考 Bash 中如何将 stderr 重定向到 stdout。
输入重定向的方向则相反。< 操作符将文件连接到 stdin:
sort < names.txt
这里 sort 从 names.txt 读取数据,而不是从终端读取。许多命令也接受文件名作为参数,但输入重定向适用于任何设计为从 stdin 读取数据的命令。
4. 管道如何使用这些数据流
管道(|)将前一个命令的 stdout 连接到后一个命令的 stdin,这样数据就可以在程序之间传递,而无需借助临时文件:
ls /etc | grep conf
ls 的 stdout 成为了 grep 的 stdin。普通管道传递的是 stdout,而不是 stderr,所以 ls 产生的错误信息仍然会显示在终端上,而不会传给 grep。关于更长的管道链、错误处理以及 pipefail 选项,可以参考 Linux 管道使用指南。如果想在终端显示输出的同时保存到文件,可以使用 tee 命令。
5. 快速参考
如需可打印的快速参考,请查看 Bash 速查表。