在 Linux,可以通过两种方式查看 Shell 命令的输出,一种是在终端屏幕显示输出,另一种将输出保存到文件。

在本教程中,我们将以实例说明 Shell 脚本中必不可少的知识,也就是标准输入、标准输出和重定向。

什么是标准输入/标准输出/标准错误

在 Linux 一切都是文件,包括输入和输出。且每个进程都可以同时打开九个文件描述符。

保留的文件描述符是 0、1、2。当运行命令时,命令启动的进程就会自动打开这三个保留的文件描述符。

0 表示标准输入,全称 standard input,简称 stdin,默认是键盘的输入,对应的文件描述符是 /proc/self/fd/0。

1 表示标准输出,全称 standard output,简称 stdout,默认是输出到屏幕,对应的文件描述符是 /proc/self/fd/1。

2 标准错误,全称 standard error,简称 stderr,默认是输出到屏幕,对应的文件描述符是 /proc/self/fd/2。

可以使用这些文件描述符来控制命令程序或者脚本的输入和输出。你需要完全理解这三个概念。

因为它是 Shell 脚本或者程序的支柱。因此,我们将以实例的形式详细描述它们中的每一个,帮助你理解。

标准输入

STDIN 标准输入,也就是文件描述符 1,默认为键盘。可以使用重定向符号 < 指定文件作为标准输入。

如果你将标准输入替换为文件,重定向符号 < 会将文件数据作为标准输入传递给指定的命令。

例如命令 cat < archive.tar | gzip -c > archive.tar.gz 使用重定向符号 < 将archive.tar 文件作为标准输入传递给 cat 命令。

cat 命令接收标准输入后,又将 archive.tar 文件内容写入标准输出,最后通过管道传递给gzip命令进行压缩。

cat < archive.tar | gzip -c > archive.tar.gz

标准输出

STDOUT  标准输出,也就是文件描述符 2,默认为屏幕。可以使用重定向符号 >>,> 将标准输出重定向到文件。

也可以使用重定向符号 >> 将数据追加到指定文件。例如命令 pwd >> log 会将 pwd 命令的标准输出追加到 log 文件。

而重定向符号 > 则会使用标准输出覆盖指定文件。例如命令 > filename 将会清空文件 filename。

pwd >> log
> filename

标准错误

ls file2 > file 命令尝试使用重定向 > 符号将标准输出重定向到文件。如果当前目录没有 file2 文件。

ls 命令将会打印一个错误消息 ls: cannot access 'file2': No such file or directory,这通常称为标准错误 stderr。

默认情况下,Shell 将标准错误发送到屏幕。如果您需要将标准错误重定向到日志文件,可以使用重定向符号 > / >> 重定向错误。

ls file2
ls: cannot access 'file2': No such file or directory

重定向错误

正如引言所提到的,2 是标准错误,因此我们将文件描述符 2 放在重定向符号之前来重定向错误。

假设当前目录不存在文件 xfile,现在运行命令 ls -l xfile 2> log 会把错误重定向到文件log,2> 表示重定向标准错误。

如您所见错误消息并没有打印在屏幕,而是写入到文件,可以运行 cat 命令查看文 log 文件的内容。

ls -l xfile 2> log
cat log

重定向错误与标准输出

要重定向标准错误和标准输出,必须在每个重定向符号之前添加正确的文件描述符。在同一命令可以使用多个重定向符号来实现标准输出和标准错误的重定向。

假设当前目录存在文件 file1,但不存在文件 file2。现在运行命令 ls flie1 file2 将会同时产生标准错误和标准输出。

出于某种原因,你可能需要标准错误写入到 error.log 文件 ,而标准输出写入 access.log 文件。

此时就可以使用文件描述 1 和重定向符号 > 写入到 access.log 文件,组合之后的符号是 1>

文件描述符 2 和重定向符号 > 写入到 error.log 文件,组合之后的符号是 2>。因此最终的命令是 ls file1 file2 2> error.log 1> access.log

ls file1 file2 2> error.log 1> access.log

除此之外还可以使用 &> 符号将标准输出和标准错误重定向到同一个文件。例如命令 ls file1 file2 &> log

你可能看到重定向标准错误与标准输出的命令,与你经常在其它教程看到的不一样。

这里为了易于理解如何重定向标准错误和标准输出,因此,这里没有说明另一个与你经常使用的命令,本教程最后一节会有提到。

ls file1 file2 2> error.log 1> access.log
ls file1 file2 &> log

永久重定向

输出重定向有两种方式临时重定向和永久重定向。对于临时重定向,可以使用 > 或者 >> 符号。如果您有很多数据需要重定向,则可以使用 exec 命令进行永久重定向。

永久重定向并不是真的就一直唯一类型的重定向,可以随时使用 exec 命令进行修改。

永久重定向相当于为进程创建一个文件描述符,接下来运行命令的标准输出,标准错误,标准输入都使用同一文件描述符。

例如 std.sh 脚本将会重定向 exec 命令之后的所有标准输出,也就是 echo 生成的标准输出。如果运行 cat 命令查看 log 文件,我们将看到 echo 命令的标准输出。

可以多次使用 exec 命令多次重定向不同的文件描述符,例如命令 exec 2> error.log 将标准错误重定向到 error.log 文件。

当你读到这里还是很难理解,请尝试运行 std.sh 脚本,理解运行顺序,运行 cat 命令查看两个文件 error.log 和 log。

#!/bin/bash
exec 1> log #永久重定向标准输出
echo "Permanent redirection"
echo "from a shell to a file."
echo "without redirecting every line"

exec 2> error.log #永久重定向标准错误
echo "Script Begining ..."
echo "Redirecting Output"
std.sh

除了使用 exec 命令重定向标准输出,标准错误。还可以使用 exec 命令重定向标准输入。默认的标准输入 STDIN,也就是文件描述符 1,通常是键盘。

exec 0< log 命令使用 log 文件作为标准输入,而不是默认的键盘。通常 Linux 系统管理员使用这种技术来读取日志文件并进行处理。

stdin.sh 脚本很简单。您应该知道如何使用read 命令获取用户输入。如果将文件重定向到标准输入,read 命令将尝试读取文件的内容。

#!/bin/bash
exec 0< testfile
total=1
while read line; do
	echo "#$total: $line"
	total=$(($total + 1))
done
stdin.sh

nohup 命令重定向标准错误和标准输出

有时候您可能不想看到任何输出。我们将所有输出重定向到黑洞,也就是空设备文件 /dev/null。这种情况在使用 nohup 命令启动后台进程时常见。

在使用 nohup 命令启动后台进程时你可能见过符号 2>&1,但很少使用这种方式 2> /dev/null 1> /dev/null 重定向到空设备文件 /dev/null

其实 ls -al file1 file2 2> /dev/null 1> /dev/nullls -al file1 file2 > /dev/null 2>&1 是不相等的命令。

2> /dev/null 1> /dev/null 是直接将标准错误重定向到空设备文件,而 > /dev/null 2>&1是将标准错误先重定向到标准输出,然后再重定向到空设备文件。

ls -al file1 file2 2> /dev/null 1> /dev/null
ls -al file1 file2 > /dev/null 2>&1

如果 2>&1 是将标准错误信息重定向到标准输出,你可能会想问 2>1 结果是什么,我们可以运行命令 ls -al file1 file2 2>1 来验证这一点。

运行命令后你可能注意到当前目录存在文件 1,运行命令 cat 1 你会发现标准错误都写入文件 1。​

也就是说 2>1 会将标准错误重定向到文件1里面,所以 2>&1&1 指的是标准输出。

ls -al file1 file2 2>1
cat 1
ls: cannot access 'file2': No such file or directory

结论

现在您了解标准输入、标准输出、标准错误以及如何重定向它们。