在启动 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
这正是我们所看到的。
数据流的意识
掌握如何判断脚本是连接到终端窗口、管道还是被重定向,可以帮助您相应地调整脚本的行为。
日志和诊断输出可以根据是输出到屏幕还是文件,选择更加详细或简洁的形式。 错误消息可以记录到与正常程序输出不同的文件中。
通常情况下,更多的知识会带来更多的选择。