在 Bash 及其他 Linux shell 中,每个正在运行的程序都会使用三个标准 I/O 流。每个流都由一个数字文件描述符表示:

  • 0 — stdin,标准输入流。
  • 1 — stdout,标准输出流。
  • 2 — stderr,标准错误流。

文件描述符是一个标识已打开的输入或输出资源的编号。在交互式会话中,stdin 通常来自终端。程序把常规输出发送到 stdout,把错误信息发送到 stderr……

关于这三个通道以及命令如何继承它们的概念性介绍,请参阅 Linux 中 stdin、stdout 与 stderr 的指南。

当你把某个命令的输出重定向到文件,或将其通过管道传给另一个命令时,可能会注意到错误信息仍然显示在屏幕上。这是因为重定向通常只影响 stdout(标准输出)……

本指南讲解如何将 stderr 重定向到 stdout、如何将两个流都重定向到文件,以及 Bash 中其他常见的重定向模式。

快速答案

要将 stderr 重定向到与 stdout 相同的文件,请在文件重定向之后加上 2>&1:

command
 > file.txt 2>
&
1

在 Bash 中,可以使用更简短的 &> 形式:

command
 
&
> file.txt

顺序很重要。command > file.txt 2>&1 会把两个流都发送到 file.txt,而 command 2>&1 > file.txt 只会把 stdout 发送到文件,stderr 仍留在终端上。

将输出重定向到文件

可以使用 n> 运算符来重定向流,其中 n 是文件描述符编号。当省略 n 时,它默认为 1(标准输出)。

下面两条命令等价。两者都把 stdout 重定向到文件:

command
 > file.txt
command
 1> file.txt

要把标准错误(stderr)重定向到文件,请使用 2> 运算符:

command
 2> error.txt

你可以把 stdout 和 stderr 分别重定向到两个不同的文件:

command
 1> output.txt 2> error.txt

将 stderr 重定向到 stdout

把程序的输出保存到文件时,通常把 stderr 重定向到 stdout,这样所有内容最终都会进入同一个文件。

要将 stderr 重定向到 stdout,使用 2>&1:

command
 > file.txt 2>
&
1

在这个例子中,> file.txt 把 stdout 重定向到 file.txt,而 2>&1 把 stderr 重定向到 stdout 当前所在的位置(也就是现在的 file.txt)。

下面是一个可以从任意目录安全运行示例。第一个路径应当存在,而第二个路径是故意不存在的:

ls /tmp /missing-path > /tmp/redirect-demo.txt 2>
&
1

查看文件内容:

cat /tmp/redirect-demo.txt

文件里同时包含 /tmp 的常规 ls 输出,以及 /missing-path 的错误信息,因为 stdout 和 stderr 都被重定向到了同一个目标。

Bash 还提供了 &> 简写形式,它用一个运算符把 stdout 和 stderr 都重定向到文件:

command
 
&
> file.txt

两种形式产生相同的结果。&> 语法更短,在 Bash 脚本中更受推荐,但 2>&1 可在所有 POSIX shell 间移植。

将 stdout 重定向到 stderr

反方向同样有用。脚本常常需要打印一条诊断信息,而这条信息不应与它所生成的数据混在一起,>&2 运算符会把这一行发送到标准错误而非标准输出:

echo
 
"config file missing"
 >
&
2

这条信息仍然会显示在你的终端上,因为 stderr 默认就输出到那里。一旦脚本的输出被重定向或通过管道传递,区别就显现出来了:诊断信息会留在数据流之外。

>&2 是 1>&2 的简写,两种形式完全相同。> 前面的描述符默认是 1,与 > file 中的行为完全一致:

echo
 
"config file missing"
 1>
&
2

你可以通过把每个流分别发送到自己的文件来观察这种分流:

bash -c 
'echo out; echo err >&2'
 > output.txt 2> error.txt

output.txt 中保存着 out,error.txt 中保存着 err。两条 echo 命令都在同一个 shell 中运行,但 >&2 把第二条信息移动到了另一个流上。

每个行为良好的脚本都遵循这一约定:结果输出到 stdout,警告和错误输出到 stderr。正是这一点,让调用方可以把你的脚本通过管道传给另一个命令,而不会被错误文本污染数据……

重定向顺序很重要

重定向运算符的顺序会改变结果。在下面的例子中,尽管看起来像是捕获了两个流,但实际上只有 stdout 被重定向到了文件:

command
 2>
&
1
 > file.txt

Bash 会从左到右求值重定向。2>&1 先把 stderr 指向 stdout 此刻所在的位置(终端),然后 > file.txt 再把 stdout 重定向到文件。stderr 保留了它较早的目标(终端)……

务必把 2>&1 放在文件重定向之后:

command
 > file.txt 2>
&
1

一个相关的错误是把同一个文件命名两次,而不是使用 2>&1:

command
 > file.txt 2> file.txt

这并不会合并两个流。每次重定向都会以自己的方式打开 file.txt,因此命令最终持有的是两个描述符,它们各自跟踪独立的写入位置,且都从字节 0 开始。无论哪个先写入……

command
 > file.txt 2>
&
1

追加而非覆盖

> 运算符每次都会覆盖目标文件。要向现有文件追加输出,请使用 >>:

command
 >> file.txt 2>
&
1

这会向 file.txt 追加 stdout,并把 stderr 重定向到同一位置。

要使用简写语法追加两个流,请使用 &>>:

command
 
&
>> file.txt

追加对于日志文件很有用,因为你希望跨多次运行累积输出。

通过管道将 stderr 传给另一个命令

默认情况下,管道运算符(|)只把 stdout 传给下一个命令。第一个命令的任何错误信息都会打印到终端,而不会通过管道发送。

关于管道语法、退出状态与 pipefail,请参阅我们的 Linux 管道与 | 运算符指南。

要通过管道传递 stdout 和 stderr,请在管道之前把 stderr 重定向到 stdout:

command
 2>
&
1
 
|
 grep 
"error"

在这个例子中,常规输出和错误信息都会被传给 grep,由它筛选出包含 “error” 的行。

Bash 还提供了 |& 简写:

command
 
|&
 grep 
"error"

两种形式等价。为了 POSIX 兼容性请使用 2>&1 |,为了在 Bash 脚本中更简练请使用 |&。

仅通过管道传递 stderr

有时你想要的恰恰相反:把错误信息通过管道传递,而把常规输出丢弃。交换两个运算符即可做到:

command
 2>
&
1
 >/dev/null 
|
 grep 
"denied"

像 Bash 那样从左到右读。当 shell 构建管道时,它先把 stdout 连接到管道,因此 2>&1 也会把 stderr 指向管道。后面的 >/dev/null 再把 stdout 移走……

下面是一个可运行的示例。第一个路径存在并产生常规输出,第二个路径不存在并产生错误:

ls /etc /missing-path 2>
&
1
 >/dev/null
ls: cannot access '/missing-path': No such file or directory

只有错误行被返回。/etc 的目录列表被发送到了 /dev/null。这是一种过滤或统计失败数量的实际方法,例如把错误通过管道传给 wc,以查看某个命令有多少个路径失败。

用 /dev/null 丢弃输出

/dev/null 是一个特殊的文件,它会丢弃所有写入其中的数据。重定向到 /dev/null 会静默输出。

要在保留常规输出的同时抑制错误信息:

command
 2> /dev/null

要抑制所有输出(stdout 和 stderr 同时):

command
 
&
> /dev/null

要只保留错误信息并丢弃常规输出:

command
 > /dev/null

这种模式常见于脚本中,你只想检查某个命令的退出状态,而不显示它的输出。

将 stderr 和 stdout 重定向到不同目标

在某些情况下,你可能希望把错误保存到日志文件,同时仍在屏幕上显示常规输出。

要只把 stderr 重定向到文件,而 stdout 正常打印:

command
 2> error.log

反之,要在屏幕上显示错误,同时把常规输出保存到文件:

command
 > output.log

要把两个流分别保存到不同的文件:

command
 > output.log 2> error.log

如果你需要同时保存和显示输出,请使用 tee 命令:

command
 2>
&
1
 
|
 tee output.log

这会把合并后的输出发送到 output.log,同时也在终端上显示。

要把 stdout 和 stderr 分别保存到不同的文件,同时在终端上显示两者,请使用进程替换:

command
 > >
(
tee output.log
)
 2> >
(
tee error.log >
&
2
)

每个流都经过自己的 tee 进程。第二个重定向里的 >&2 让错误输出继续流回终端的 stderr,因此警告仍可见。

在脚本中使用 exec 重定向全部输出

要重定向脚本余下部分中每条命令的输出,请在文件顶部使用 exec 内建命令。这样可以避免每行后面都重复写 >> log 2>&1:

#!/bin/bash

exec
 >> /var/log/backup.log 2>
&
1




echo
 
"Starting backup..."


rsync -a /data/ /backup/

echo
 
"Done."

在 exec 行之后,后续每条命令的 stdout 和 stderr 都会进入日志文件。这种模式常见于 cron 任务和长时间运行的脚本,你希望得到一个统一的合并日志,而无需改动每一行……

要重定向脚本余下部分中仅 stderr,请省略第一次重定向:

exec
 2>> /var/log/backup.err

以这种方式重定向 stdout 是单向的,除非你先保存原始目标。在重定向之前,把它复制到一个备用描述符(如 3)上:

#!/bin/bash

exec
 3>
&
1


exec
 >> /var/log/backup.log 2>
&
1




echo
 
"Starting backup..."


echo
 
"Backup started, see /var/log/backup.log"
 >
&
3




rsync -a /data/ /backup/



exec
 1>
&
3
 3>
&
-

echo
 
"Backup finished"

exec 3>&1 在 stdout 仍指向终端时,把它复制到描述符 3 上。第二条 exec 之后的所有内容都会进入日志,但显式写入 >&3 的消息会到达终端……

描述符 3 到 9 可供这类记录使用。只有 0、1、2 具有固定的含义。

快速参考

关于可打印的快速参考,请参阅 Bash 速查表。

语法说明
command > file将 stdout 重定向到文件(覆盖)
command >> file将 stdout 重定向到文件(追加)
command 2> file将 stderr 重定向到文件
command 2>> file向文件追加 stderr
command >&2将 stdout 发送到 stderr
command > file 2>&1将 stdout 和 stderr 都重定向到文件
command &> file重定向两个流的简写形式(Bash)
command &>> file向文件追加两个流(Bash)
command 2>&1 | cmd将两个流通过管道传给另一个命令
command 2>&1 >/dev/null | cmd仅将 stderr 通过管道传给另一个命令
command |& cmd通过管道传递两个流的简写形式(Bash)
command 2> /dev/null丢弃错误信息
command &> /dev/null丢弃所有输出
exec >> file 2>&1重定向脚本余下部分的全部输出
exec 3>&1把当前 stdout 保存到描述符 3

常见问题

> 和 >> 有什么区别?> 运算符会覆盖目标文件,而 >> 会向其中追加。当你想向现有文件添加输出而不擦除其内容时,请使用 >>。

2>&1 是什么意思?它把文件描述符 2(stderr)重定向到与文件描述符 1(stdout)相同的目标。1 前面的 & 告诉 Bash,1 是一个文件描述符,而不是文件名。

什么是 /dev/null?/dev/null 是一个特殊的设备文件,会丢弃所有写入其中的数据。把输出重定向到 /dev/null 实际上会让命令静默。

&> 和 2>&1 一样吗?在 Bash 中,command &> file 等价于 command > file 2>&1。两者都把 stdout 和 stderr 重定向到同一个文件。不过,&> 是 Bash 的扩展,并非在所有 POSIX shell 中都可用……

如何在 Bash 脚本中输出到 stderr?在命令后面追加 >&2,例如 echo "error message" >&2。完整形式是 1>&2,两者相同。把它用于警告和错误,这样调用方就可以通过管道……

如何在管道中将 stderr 重定向到 stdout?使用 command 2>&1 | next_command 或 Bash 简写 command |& next_command。两者都会通过管道传递 stderr 和 stdout。

我能否只通过管道传递 stderr 而丢弃 stdout?可以。交换两个运算符:command 2>&1 >/dev/null | grep pattern。先做 2>&1,把 stderr 指向管道,然后 >/dev/null 丢弃 stdout。

总结

将 stderr 和 stdout 重定向,有助于你捕获日志、过滤错误,并在需要时让脚本保持安静。为了可移植的 shell 语法请使用 2>&1,为了 Bash 简写请使用 &> 或 |&,当脚本需要向调用方报告时请使用 >&2……