第六章:重定向

在本课中,我们将释放命令行最酷的功能。这被称为I/O重定向(I/O redirection)。“I/O”代表输入/输出(input/outpu),通过此功能,我们可以将命令的输入和输出重定向到文件或从文件重定向,以及将多个命令连接在一起,形成强大的命令管道(pipelines)。为了展示此功能,我们将介绍以下命令:

第六章:重定向标准输入输出和错误重定向标准输出组命令重定向标准错误将标准输出和标准错误重定向到一个文件处理不需要的输出Unix文化中的 /dev/null重定向标准输入cat —— 连接文件管道>| 的区别过滤器uniq —— 报告或省略重复行wc —— 打印行、单词,和字节计数grep —— 打印与范式匹配的行head / tail —— 打印文件的头部/尾部tee —— 从Stdin读取并输出到Stout和文件总结Linux是关于想象力的

标准输入输出和错误

到目前为止,我们使用的许多程序都会产生某种输出。此输出通常由两种类型组成:

如果我们查看 ls 这样的命令,我们可以看到它在屏幕上显示其结果和错误消息。

遵循Unix的主题【一切都是文件】, ls 等程序实际上会将结果发送到一个名为标准输出(standard output ,通常表示为 stdout )的特殊文件,并将状态消息发送到另一个称为标准错误(standard errorstderr )的文件。默认情况下,标准输出和标准错误都链接到屏幕,不保存到磁盘文件中。

此外,许多程序从称为标准输入(standard inputstdin )的工具中获取输入,默认情况下,该工具连接到键盘。

I/O重定向允许我们更改输出的去向和输入的来源。通常,输出会显示在屏幕上,输入来自键盘,但通过I/O重定向,我们可以改变这一点。

重定向标准输出

I/O重定向允许我们重新定义标准输出的去向。为了将标准输出重定向到另一个文件而不是屏幕,我们使用>重定向运算符,后跟文件名。我们为什么要这样做?将命令的输出存储在文件中通常很有用。例如,我们可以告诉shell将 ls 命令的输出发送到文件 ls-output.txt ,而不是屏幕:

在这里,我们创建了一个 /usr/bin 目录的长列表,并将结果发送到文件 ls-output.txt 。让我们检查一下命令的重定向输出,如下所示:

很好——一个漂亮的大文本文件。如果我们用 less 命令查看文件,我们会看到文件 ls-output.txt 确实包含 ls 命令的结果:

现在,让我们重复我们的重定向测试,但这次有一个转折。我们将把目录的名称更改为不存在的名称:

我们收到一条错误消息。这是有道理的,因为我们指定了不存在的目录 /bin/usr ,但为什么错误消息显示在屏幕上,而不是重定向到文件 ls-output.txt ?答案是 ls 程序不会将错误消息发送到标准输出。相反,与大多数编写良好的Unix程序一样,它将错误消息发送到标准错误(standard error - stderr)。由于我们只重定向了标准输出而不是标准错误,因此错误消息仍被发送到屏幕上。我们将在一分钟后看到如何重定向标准错误,但首先让我们看看输出文件发生了什么:

文件现在长度为零!这是因为当我们使用 > 重定向运算符重定向输出时,目标文件总是从头重写。由于我们的 ls 命令没有生成任何结果,只生成了一条错误消息,重定向操作开始重写文件,然后由于错误而停止,导致文件被截断。事实上,如果我们真的需要截断一个文件(或创建一个新的空文件),我们可以使用这样的技巧:

只需使用重定向运算符,前面没有命令,就会截断现有文件或创建一个新的空文件。

那么,我们如何将重定向的输出追加(append)到文件中,而不是从头覆盖文件呢?为此,我们使用 >> 重定向运算符,如下所示:

使用 >> 运算符将导致输出被附加到文件中。如果文件不存在,则创建它就像使用了 > 运算符一样。让我们通过重复一个命令并将其输出附加到一个文件来进行测试:

我们重复 ls 命令三次,得到一个三倍大的输出文件。

组命令

让我们想象一种情况,我们想执行一系列命令并将结果发送到日志文件。既然我们已经知道了,我们可以这样做:

此序列中的第一个命令创建/截断一个名为 logfile.txt 的文件,随后的每个命令都将其输出附加到该文件中。这种技术会奏效,但打了很多多余的字。一定有更好的办法。

正如我们在上一章中看到的,我们可以将多个命令放在一行上,如下所示:

因此,我们可以将所有命令和重定向放在一行上:

但是,如果我们可以将序列视为具有单个输出流的单个实体呢?我们可以通过创建组命令(group command)来实现这一点。为此,我们用大括号字符包围我们的序列:

当我们的序列被大括号包围时,shell会将其视为重定向的单个命令。请注意,shell要求大括号周围有空格,序列中的最后一个命令必须以分号或换行符终止

重定向标准错误

重定向标准错误缺乏专用重定向运算符的易用性。要重定向标准错误,我们必须引用其文件描述符(file descriptor)。程序可以在几个编号的文件流中的任何一个上产生输出。虽然我们将前三个文件流称为标准输入、输出和错误,但shell在内部分别将它们称为文件描述符0、1和2。shell提供了一种使用文件描述符编号重定向文件的符号。由于标准错误与文件描述符编号2相同,我们可以用以下符号重定向标准错误:

文件描述符“2”位于重定向运算符之前,用于将标准错误重定向到文件 ls-error.txt

将标准输出和标准错误重定向到一个文件

在某些情况下,我们可能希望将命令的所有输出捕获到一个文件中。为此,我们必须同时重定向标准输出和标准错误。有两种方法可以做到这一点。这里显示的是传统方式,适用于旧版本的shell:

使用这种方法,我们执行两次重定向。首先,我们将标准输出重定向到文件 ls-output.txt ,然后使用符号 2>&1 将文件描述符2(标准错误)重定向到文件描述符1(标准输出)。

请注意,重定向的顺序很重要。标准错误的重定向必须始终在重定向标准输出后发生,否则将不起作用。以下示例将标准错误重定向到文件 ls-output.txt

但是,如果顺序更改为以下顺序,则标准错误将显示在屏幕上:

最近版本的bash提供了第二种更简化的方法来执行这种组合重定向,如下所示:

在这个例子中,我们使用单一符号 &> 将标准输出和标准错误重定向到文件 ls-output.txt 。我们还可以将标准输出和标准错误流附加到一个文件中,如下所示:

处理不需要的输出

有时“沉默是金”(silence is golden),我们不希望命令输出,我们只想把它扔掉。这尤其适用于错误和状态消息。系统提供了一种方法,将输出重定向到一个名为 /dev/null 的特殊文件。这个文件是一个系统设备,通常被称为比特桶(bit bucket),它接受输入,什么也不做。为了抑制命令中的错误消息,我们这样做:

Unix文化中的 /dev/null

比特桶是一个古老的Unix概念,由于其普遍性,它出现在Unix文化的许多地方。当有人说他/她正在将你的评论发送到 /dev/null 时,现在你知道这意味着什么了。有关更多示例,请参阅 维基百科上关于 /dev/null 的文章

重定向标准输入

到目前为止,我们还没有遇到任何使用标准输入的命令(实际上我们有,但稍后我们会透露这个惊喜),所以我们需要介绍一个。

cat —— 连接文件

cat命令读取一个或多个文件并将其复制到标准输出,如下所示:

cat [file ...]

在大多数情况下,我们可以将 cat 视为类似于DOS中的 TYPE 命令。

我们可以使用它来显示文件,而无需分页。例如,以下内容将显示文件 ls-output.txt 的内容:

cat 常用于显示短文本文件。由于 cat 可以接受多个文件作为参数,因此它也可以用于将文件连接在一起。假设我们下载了一个被拆分为多个部分的大文件(多媒体文件在Usenet上通常以这种方式拆分),我们想将它们重新连接在一起。如果文件命名为:

movie.mpeg.001 movie.mpeg.002 ... movie.mpeg.099

我们可以使用以下命令将它们重新连接在一起:

由于通配符总是按排序顺序展开,因此参数将按正确的顺序排列。

这一切都很好,但这与标准输入有什么关系呢?还没有,但让我们试试别的。如果我们毫无异议地进入 cat ,会发生什么?

什么也没发生,它只是像挂着一样坐在那里。这可能看起来是这样,但它确实在做它应该做的事情。

如果没有给 cat 任何参数,它就会从标准输入中读取,由于默认情况下标准输入是附加在键盘上的,它正在等待我们键入一些东西!尝试添加以下文本并按Enter键:

接下来,键入Ctrl - d (即按住Ctrl键并按“d”),告诉 cat 已经到达标准输入的文件末尾(end of file ,EOF):

在没有文件名参数的情况下, cat 会将标准输入复制到标准输出,因此我们看到我们的文本行重复。我们可以使用此行为创建短文本文件。假设我们想创建一个名为 lazy_dog.txt 的文件,其中包含我们示例中的文本。我们将这样做:

键入命令,然后键入要放入文件中的文本。记得在末尾键入 Ctrl - d 。使用命令行,我们实现了世界上最愚蠢的(dumbest)文字处理器!为了查看我们的结果,我们可以使用 cat 再次将文件复制到stdout。

现在我们知道了 cat 如何接受标准输入,除了文件名参数外,让我们尝试重定向标准输入。

使用 < 重定向运算符,我们将标准输入的来源从键盘更改为 lazy_dog.txt 文件。我们看到结果与传递单个文件名参数相同。与传递文件名参数相比,这并不是特别有用,但它可以用来演示如何将文件用作标准输入源。其他命令更好地利用了标准输入,我们很快就会看到。

在我们继续之前,请查看 cat 的手册页,因为它有几个有趣的选项。

管道

命令从标准输入读取数据并发送到标准输出的能力被称为管道(pipelines)的shell功能所利用。使用管道运算符 |(竖线),一个命令的标准输出可以通过管道传输到(be piped)另一个命令。

command1 | command2

为了充分证明这一点,我们需要一些命令。还记得我们说过有一个我们已经知道的接受标准输入的吗?这就是 less 。我们可以使用 less 逐页显示任何将结果发送到标准输出的命令的输出:

这非常方便!使用这种技术,我们可以方便地检查任何产生标准输出的命令的输出。

>| 的区别

乍一看,可能很难理解管道运算符 | 与重定向运算符 > 执行的重定向。简单地说:

很多人在学习管道时会尝试以下方法,“只是为了看看会发生什么”:

command1 > command2

答案是:有时候真的很糟糕。

这是一个由管理基于Linux的服务器设备的读者提交的实际示例。作为超级用户,他这样做了:

第一个命令将他放在存储大多数程序的目录中,第二个命令告诉shell用 ls 命令的输出覆盖文件 less 。由于 /usr/bin 目录中已经包含了一个名为 less 的文件(less 程序),第二个命令用 ls 中的文本覆盖了 less 程序文件,从而破坏了系统上的 less 程序。

这里的教训是, > 重定向运算符会自动创建或覆盖文件,因此您需要非常尊重它。

过滤器

管道通常用于对数据执行复杂的操作。可以将多个命令放在一个管道中。通常,这种方式使用的命令被称为过滤器(filters)。过滤器接收输入,以某种方式(somehow)更改它,然后输出它。我们将尝试的第一个是 sort (排序)。想象一下,我们想制作一个 /bin/usr/bin 中所有可执行程序的组合列表,将它们按排序顺序排列,并查看结果列表:

由于我们指定了两个目录(/bin/usr/bin), ls 的输出将由两个排序列表组成,每个目录一个。通过在管道中包含 sort ,我们更改了数据以生成一个单独的排序列表。

sort 是一个功能强大的命令,具有许多功能和选项。我们将在第20章中详细介绍它们。

uniq —— 报告或省略重复行

uniq 命令通常与 sort 结合使用。 uniq 接受来自标准输入或单个文件名参数的排序数据列表(有关详细信息,请参阅 uniq 手册页),默认情况下,会从列表中删除任何重复项。因此,为了确保我们的列表没有重复项(即出现在 /bin/usr/bin 目录中的任何同名程序),我们将在管道中添加 uniq

在这个例子中,我们使用 uniqsort 命令的输出中删除任何重复项。如果我们想看到重复项列表,我们可以在 uniq 中添加 -d 选项,如下所示:

wc —— 打印行、单词,和字节计数

wc( word count,单词计数)命令用于显示文件中包含的行数、单词数和字节数。这里有一个例子:

在这种情况下,它会打印出三个数字:行(lines)数、单词(words)数和 ls-output.txt 中包含的字节数。与我们之前的命令一样,如果在没有命令行参数的情况下执行, wc 接受标准输入。 -l 选项将其输出限制为仅报告行。将其添加到管道中是一种方便的计数方法。要查看排序列表中的项目数量,我们可以这样做:

grep —— 打印与范式匹配的行

grep是一个功能强大的程序,用于在文件中查找文本范式(text patterns)。它是这样使用的:

grep pattern [file ...]

grep 在文件中遇到【范式】时,它会打印出包含它的行。 grep 可以匹配的范式可能非常复杂,但现在我们将专注于简单的文本匹配。我们将在第19章介绍高级模式,称为正则表达式(regular expressions)。

假设我们想找到程序列表中名称中嵌入单词 zip 的所有文件。这样的搜索可能会让我们了解系统中与文件压缩有关的一些程序。我们将这样做:

以下是grep的一些方便的选项:

head / tail —— 打印文件的头部/尾部

有时我们不希望命令的所有输出。我们可能只想要前几行或最后几行。 head 命令打印文件的前十行, tail 命令打印最后十行。虽然这两个命令默认打印十行文本,但可以使用 -n 选项指定行数。

这些命令也可以在管道中使用:

-n 选项与 headtail 一起使用,可以从文件中间剪切摘录。让我们假设我们有一个文本文件,它有一个5行的页眉和一个5行尾,我们想删除它,只留下中间包含数据的“好”部分。我们可以这样做:

当与 head 一起使用时, -n 选项允许输出负值,这会导致除最后 n 行之外的所有行都被输出。类似地, tail-n 选项允许一个加号,导致除前 n 行之外的所有行都被输出。

tail 还有一个选项,允许我们实时跟踪文件的内容。这对于在写入日志文件时查看日志文件的进度非常有用。在下面的示例中,我们将查看 /var/log 中的消息文件(如果缺少 messages ,则查看 /var/log/syslog 文件)。在某些Linux发行版上执行此操作可能需要超级用户权限,因为日志文件可能包含安全信息:

使用 -f 选项, tail 会连续监视文件,当添加新行时,它们会立即出现在显示器上。这会一直持续到我们按 Ctrl-c

tee —— 从Stdin读取并输出到Stout和文件

为了与我们的管道比喻保持一致,Linux提供了一个名为 tee 的命令,可以在我们的管道上创建一个“tee”配件。 tee 程序读取标准输入并将其复制到标准输出(允许数据继续沿管道传输)和一个或多个文件。这对于在处理的中间阶段捕获管道的内容非常有用。在这里,我们重复前面的一个示例,这次包括 tee ,在 grep 过滤管道内容之前,将整个目录列表捕获到文件 ls.txt 中:

总结

与往常一样,请查看本章中介绍的每个命令的文档。我们只看到了它们最基本的用法。他们都有一些有趣的选择。随着我们获得Linux经验,我们将看到命令行的重定向功能对于解决特殊问题非常有用。有许多命令使用标准输入和输出,几乎所有的命令行程序都使用标准错误来显示其信息性消息。

Linux是关于想象力的

当我被要求解释Windows和Linux之间的区别时,我经常用一个玩具类比。

Windows就像一个Game Boy。你去商店买一个盒子里闪闪发光的新东西。你把它带回家,打开它,玩它。漂亮的图形,可爱的声音。不过,过了一段时间,你会厌倦随附的游戏,所以你会回到商店再买一个。这个循环一次又一次地重复。最后,你回到商店,对柜台后面的人说:“我想要一款这样的游戏!”却被告知不存在这样的游戏,因为没有“市场需求”。然后你说,“但我只需要改变一件事!”柜台后面的人说你不能改变它。游戏都密封在墨盒里。你发现你的玩具仅限于其他人决定你需要的游戏。

另一方面,Linux就像世界上最大的安装程序集。你打开它,它只是一个巨大的零件集合。有很多钢支柱、螺钉、螺母、齿轮、滑轮、电机,以及一些关于建造什么的建议。所以,你开始玩它。你构建一个建议,然后是另一个。过了一段时间,你发现你对做什么有自己的想法。你永远不必回到商店,因为你已经有了你需要的一切。竖立器套装呈现出你想象中的形状。它做你想做的事。

当然,你对玩具的选择是个人的事情,那么你会觉得哪种玩具更令人满意呢?