Linux 上的标准输入、标准输出和标准错误是什么?

在启动 Linux 命令时,会创建三个数据流:标准输入(stdin)、标准输出(stdout)和标准错误(stderr)。 这些数据流可以帮助我们判断脚本是否正在通过管道传输或重定向。 以下将详细介绍如何使用它们。

连接两端的流动

在接触 Linux 和类 Unix 操作系统时,我们常常会遇到 stdin、stdout 和 stderr 这些术语。 它们是执行 Linux 命令时建立的三个标准数据流。 在计算机科学中,流是数据传输的通道,而这些数据通常是文本信息。

数据流就像水流一样,有起点和终点。 每个 Linux 命令都负责提供数据流的一端,而另一端则由启动该命令的 shell 决定。 根据命令行的具体指示,另一端可能会连接到终端窗口、连接到管道,或者被重定向到文件或其他命令。

Linux 的标准数据流

在 Linux 系统中,stdin 代表标准输入流,用于接收文本数据作为输入。 命令产生的文本输出会通过 stdout(标准输出流)传输。 而命令执行过程中产生的错误消息则会通过 stderr(标准错误流)发送。

由此可见,存在两个输出流(stdout 和 stderr)和一个输入流(stdin)。 由于错误消息和正常输出都有各自的传输通道,所以它们可以被独立地处理和传递到终端窗口。

流被视为文件

在 Linux 中,流(与几乎所有其他事物一样)都被视为文件。 我们可以从文件中读取文本,也可以将文本写入文件。 这两个操作都涉及到数据流的传输,因此,将数据流视为文件处理的概念并非不合理。

每个与进程关联的文件都会被分配一个唯一的编号,称为文件描述符,用于标识该文件。 当需要对文件执行操作时,会使用 文件描述符 来定位该文件。

以下是标准输入、标准输出和标准错误流的固定文件描述符值:

0:标准输入

1:标准输出

2:标准错误

对管道和重定向的响应

为了便于人们理解某个主题,一种常见的做法是先介绍一个简化版本。 例如,在学习语法时,我们可能会被告知“i 在 e 之前,除了 c 之后”。 但实际上,这条规则的例外远多于符合它的情况。

类似地,在讨论标准输入、标准输出和标准错误时,一个方便的公理是,进程通常不会知道或关心它的标准流的最终目的地。 进程是否应该在意它的输出是发送到终端还是被重定向到文件中? 它又如何判断它的输入是来自键盘还是通过管道来自其他进程?

事实上,进程是可以知道的,或者至少可以通过检查来发现,而且如果软件开发者愿意,还可以根据不同的情况调整程序的行为。

这种行为变化很容易观察到。 尝试以下两个命令:

ls

ls | cat

如果 `ls` 命令的输出(stdout)通过管道传输到另一个命令,`ls` 命令的行为会有所不同。 将输出切换为单列的是 `ls` 命令本身,而不是 `cat` 命令。 当输出被重定向时,`ls` 也会执行相同的操作:

ls > capture.txt

cat capture.txt

重定向标准输出和标准错误

通过单独的流传输错误消息的好处在于,我们可以将命令的输出(stdout)重定向到一个文件,同时仍然可以在终端窗口中看到任何错误消息(stderr)。 这使得我们可以在错误发生时立即做出反应,并且可以避免错误消息污染标准输出所重定向的文件。

在文本编辑器中输入以下文本,并将其保存为名为 `error.sh` 的文件:

#!/bin/bash

echo "About to try to access a file that doesn't exist"
cat filename.txt

使用以下命令使脚本可执行:

chmod +x error.sh

脚本的第一行通过 stdout 流将文本回显到终端窗口。 第二行尝试访问一个不存在的文件,这将产生通过 stderr 传递的错误消息。

使用以下命令运行脚本:

./error.sh

可以看到,stdout 和 stderr 这两个输出流都显示在了终端窗口中。

现在,尝试将输出重定向到一个文件:

./error.sh > capture.txt

通过 stderr 传递的错误消息仍然被发送到终端窗口。 我们可以查看文件的内容,以确认 stdout 输出是否已写入到文件中。

cat capture.txt

标准输出已按预期重定向到了文件。

> 重定向符号默认与标准输出一起使用。 你可以使用数字文件描述符来指定需要重定向的标准输出流。

要显式重定向标准输出,请使用以下重定向指令:

1>

要显式重定向 stderr,请使用以下重定向指令:

2>

让我们再次尝试之前的测试,这次使用 `2>`:

./error.sh 2> capture.txt

错误消息被重定向,而 stdout 回显消息则被发送到了终端窗口:

标准错误消息已按预期写入 `capture.txt` 文件中。

同时重定向标准输出和标准错误

既然可以将 stdout 或 stderr 独立地重定向到不同的文件,我们自然也可以同时将它们重定向到两个不同的文件。

以下命令将 stdout 指向名为 `capture.txt` 的文件,而将 stderr 指向名为 `error.txt` 的文件:

./error.sh 1> capture.txt 2> error.txt

由于输出流(标准输出和标准错误)都被重定向到了文件,因此终端窗口没有任何输出。 我们会直接返回到命令行提示符,就像什么都没发生一样。

让我们检查一下每个文件的内容:

cat capture.txt
cat error.txt

将 stdout 和 stderr 重定向到同一个文件

到目前为止,我们已经将每个标准输出流写入到了它们各自的文件中。 剩下的唯一组合就是将 stdout 和 stderr 都发送到同一个文件。

我们可以使用以下命令实现:

./error.sh > capture.txt 2>&1

让我们来分解一下这个命令:

`./error.sh`:启动 `error.sh` 脚本文件。

`> capture.txt`:将标准输出流重定向到 `capture.txt` 文件。 `>` 是 `1>` 的简写形式。

`2>&1`:使用 `&>` 重定向指令。 此指令允许您告诉 shell 将一个流重定向到与另一个流相同的目的地。 在本例中,我们表示“将流 2(stderr)重定向到流 1(stdout)的重定向目的地”。

没有可见的输出,这说明重定向成功。

让我们检查一下 `capture.txt` 文件,看看里面有什么内容:

cat capture.txt

可以看到,stdout 和 stderr 流都被重定向到了同一个目标文件。

若要重定向流的输出并将其静默丢弃,请将输出重定向到 `/dev/null`。

在脚本中检测重定向

我们已经讨论了命令如何检测到是否有任何流被重定向,并可以选择相应地改变其行为。 那么我们可以在自己的脚本中实现这一点吗? 当然可以。 这是一种非常容易理解和使用的技术。

在编辑器中输入以下文本,并将其保存为 `input.sh`:

#!/bin/bash

if [ -t 0 ]; then

  echo stdin coming from keyboard
 
else

  echo stdin coming from a pipe or a file
 
fi

使用以下命令使脚本可执行:

chmod +x input.sh

其中的关键在于方括号内的测试。 `-t`(终端)选项在文件与文件描述符相关联,并且 在终端窗口中终止时返回 true (0)。 我们使用文件描述符 0 作为测试的参数,它代表标准输入。

如果标准输入连接到终端窗口,则测试将为真。 如果 stdin 连接到文件或管道,则测试将为假。

可以使用任何方便的文本文件来生成脚本的输入。 这里我们使用一个名为 `dummy.txt` 的文件。

./input.sh < dummy.txt

输出表明,脚本识别到输入不是来自键盘,而是来自文件。 如果需要,可以据此改变脚本的行为。

这是通过文件重定向实现的效果。 现在,让我们试试管道。

cat dummy.txt | ./input.sh

脚本识别到其输入正在通过管道传递。 更准确地说,它再次识别到标准输入流没有连接到终端窗口。

现在,让我们在既不使用管道也不使用重定向的情况下运行脚本:

./input.sh

标准输入流已连接到终端窗口,脚本也相应地报告了这一点。

为了检查输出流是否相同,我们需要一个新的脚本。 在编辑器中输入以下内容,并将其保存为 `output.sh`:

#!/bin/bash

if [ -t 1 ]; then

echo stdout is going to the terminal window
 
else

echo stdout is being redirected or piped
 
fi

使用以下命令使脚本可执行:

chmod +x output.sh

此脚本唯一的主要更改是方括号内的测试。 我们使用数字 1 来表示标准输出的文件描述符。

让我们来试试看。 我们通过 `cat` 管道输出:

./output.sh | cat

该脚本识别到它的输出不会直接进入终端窗口。

我们还可以通过将输出重定向到一个文件来测试该脚本:

./output.sh > capture.txt

终端窗口没有任何输出,我们静默地返回到命令提示符,正如我们所预期的那样。

我们可以查看 `capture.txt` 文件,以查看捕获到的内容。 使用以下命令来执行此操作:

cat capture.txt

同样,我们脚本中的简单测试检测到标准输出流没有直接发送到终端窗口。

如果在没有任何管道或重定向的情况下运行脚本,它应该检测到标准输出正在直接传递到终端窗口:

./output.sh

这正是我们所看到的。

数据流的意识

掌握如何判断脚本是连接到终端窗口、管道还是被重定向,可以帮助您相应地调整脚本的行为。

日志和诊断输出可以根据是输出到屏幕还是文件,选择更加详细或简洁的形式。 错误消息可以记录到与正常程序输出不同的文件中。

通常情况下,更多的知识会带来更多的选择。