内容


学习 Linux,101

文本流和过滤器

使用 GNU textutils 在命令行上处理文本

Comments

系列内容:

此内容是该系列 # 部分中的第 # 部分: 学习 Linux,101

敬请期待该系列的后续内容。

此内容是该系列的一部分:学习 Linux,101

敬请期待该系列的后续内容。

概述

本教程将介绍过滤器,您可以构建复杂的管道 来使用过滤器处理文本。您将学习如何显示文本,对它排序,统计字数和行数,以及翻译字符等。您还将学习如何使用流编辑器 sed

在本教程中,您将学习以下主题:

  • 通过文本实用工具过滤器发送文本文件和输出流,以修改输出
  • 使用 GNU textutils 包中的标准 UNIX 命令
  • 使用编辑器 sed 将复杂的更改编写到文本文件中

本教程可帮助您针对 Linux Server Professional (LPIC-1) 考试 101 的主题 103 中的目标 103.2 进行应考准备。该目标的权重为 3。

前提条件

要从本系列教程中获得最大收获,您应该掌握 Linux 的基本知识并拥有一个正常工作的 Linux 系统,您可以在该系统上实践本教程中涵盖的命令。有时,程序的不同版本会获得不同的输出格式,所以您的结果可能并不总是与这里给出的清单和图完全相同。

文本过滤

文本过滤是获得输入文本流并在文本上执行某种转换,然后将它发送到输出流的过程。尽管输入或输出可能来自同一个文件,但在 Linux 和 UNIX® 环境中,过滤通常是通过构造一个命令管道来完成的,在管道内传输重定向 来自一个命令的输出,以便将其用作下一个命令的输入。管道和重定向将在介绍流、管道和重定向 的教程(可在 系列路线图 中找到)中更全面地介绍,就目前而言,我们将使用 | 和 > 运算符来查看管道和基本的输出重定向。

除非另行说明,否则本教程中的示例使用的是包含 3.16 内核的 Ubuntu 14.04.2 LTS 和 3.16。您在其他系统上的结果可能有所不同。

是一个可使用库函数读或写的字节序列,它向应用程序隐藏了底层设备的细节。同一个程序可以使用流,以独立于设备的方式从或向终端、文件或网络套接字读或写数据。现代编程环境和 shell 使用了 3 种标准 I/O 流:

  • stdin标准输入流,它向命令提供输入。
  • stdout标准输出流,它将显示来自命令的输出。
  • stderr标准错误流,它将显示来自命令的错误输出。

使用 | 进行管道传输

输入可以来自您提供给命令的参数,输出可以显示在您的终端上。许多文本处理命令(过滤器)可以从标准输入流或文件获取输入。为了使用命令 command1 的输出作为过滤器 command2 的输入,需要使用管道运算符 (|) 连接这两个命令。清单 1 展示了如何传输 echo 的输出对一个较小的单词列表进行排序。

清单 1. 将来自 echo 的输出传输到 sort 的输入
ian@Z61t-u14:~$ echo -e "apple\npear\nbanana"|sort
apple
banana
pear

每个命令都可以包含一些选项或参数。您还可以使用 | 将管道中第二个命令的输出重定向到第三个命令,依此类推。构造分别拥有有限功能的命令的长管道,这是 Linux 和 UNIX 完成任务的一种常见方式。有时您还会看到使用连字符 (-) 代替文件名来作为命令的参数,这表示输入应该来自 stdin 而不是文件。

使用 > 执行输出重定向

尽管创建多个命令的管道并在终端上查看输出已经很不错,但有时候,您可能希望将输出保存到文件中。可以使用输出重定向运算符 (>) 实现此目的。

在本节的剩余部分中,会使用一些小文件,所以我们将创建一个名为 lpi103-2 的目录并使用 cd 进入该目录。然后使用 > 将 echo 命令的输出重定向到一个名为 text1 的文件。此过程如 清单 2 所示。您可以注意到,输出没有显示在终端上,因为它已被重定向到该文件。

清单 2. 将来自命令的输出重定向到文件
ian@Z61t-u14:~$ mkdir lpi103-2
ian@Z61t-u14:~$ cd lpi103-2
ian@Z61t-u14:~/lpi103-2$ echo -e "1 apple\n2 pear\n3 banana" > text1

现在我们有两个基本工具来执行管道传输和重定向,接下来,让我们查看一些常见的 UNIX 和 Linux 文本处理命令和过滤器。本节将介绍一些基本功能;请查阅合适的手册页来进一步了解这些命令。

Cat、od 和 split

现在,您已经创建了 test1 文件,您可能希望检查它的内容。使用 catconcatenate(串联)的缩写)命令在 stdout 上显示文件的内容。清单 3验证上面创建的文件的内容。

清单 3. 使用 cat 显示文件内容
ian@Z61t-u14:~/lpi103-2$ cat text1
1 apple
2 pear
3 banana

如果没有指定文件名(或者已经指定 - 作为文件名),cat 命令会从 stdin 获取输入该文件名。我们结合使用此命令和输出重定向来创建另一个文本文件,如 清单 4 所示。

清单 4. 使用 cat 创建文本文件
ian@Z61t-u14:~/lpi103-2$ cat >text2
9       plum
3       banana
10      apple

在 清单 4中,cat 不断从 stdin 读取数据,直到文件末尾处。使用 Ctrl-d(按住 Ctrl 并按下 d)组合键来表示文件结束。您还可以使用同一个组合键从 bash shell 退出。使用制表符键将水果名称排成一列。

还记得 catconcatenate 的缩写吗?可以使用 cat 将多个文件串联在一起来显示。清单 5 显示了我们刚创建的两个文件。

清单 5. 使用 cat 串联两个文件
ian@Z61t-u14:~/lpi103-2$ cat text*
1 apple
2 pear
3 banana
9	plum
3	banana
10	apple

使用 cat 显示这两个文本文件时,您会注意到排列差异 (alignment differences)。要了解导致此结果的原因,需要查看文件中的控制字符。这些字符以文本显示形式显示输出,而不是显示控制字符本身的某种表示,所以我们需要采用一种允许您查找和解释这些特殊字符的格式来转储 该文件。GNU 文本实用工具提供了一个 od(或 Octal Dump)命令来实现此用途。

od 有多个选项,比如 -A 选项控制文件偏移的基数,-t 控制显示的文件内容的形式。基数可指定为 o(八进制,默认值)、d(十进制)、x(十六进制)或 n(不显示偏移)。您可以将输出显示为八进制、十六进制、十进制、浮点、具有反斜杠转义的 ASCII 或指定的字符(nl 表示换行符,ht 表示水平制表符等)。清单 6 显示了一些可用于转储 text2 示例文件的格式。

清单 6. 使用 od 转储文件
ian@Z61t-u14:~/lpi103-2$ od text2
0000000 004471 066160 066565 031412 061011 067141 067141 005141
0000020 030061 060411 070160 062554 000012
0000031
ian@Z61t-u14:~/lpi103-2$ od -A d -t c text2
0000000   9  \t   p   l   u   m  \n   3  \t   b   a   n   a   n   a  \n
0000016   1   0  \t   a   p   p   l   e  \n
0000025
ian@Z61t-u14:~/lpi103-2$ od -A n -t a text2
   9  ht   p   l   u   m  nl   3  ht   b   a   n   a   n   a  nl
   1   0  ht   a   p   p   l   e  nl

备注:

  1. cat-A 选项提供了一种查看制表符和行尾位置的替代方式。请参阅手册页来了解有关的更多信息。
  2. 如果您在自己的 text2 文件中看到空格而不是制表符,请参阅本教程后面的 Expand、unexpand 和 tr,了解如何在文件中的制表符和空格之间切换。
  3. 如果您拥有大型机背景,那么您可能会对 hexdump 实用工具感兴趣,它包含在不同的实用工具集中。这里没有介绍它,所以请查阅手册页。

我们的示例文件非常小,但有时您拥有需要拆分为更小部分的大型文件。例如,您可能希望将一个打文件分解为 CD 大小的数据块,以便可以将它们写入 CD,通过邮件将它发送给可以为您创建 DVD 的人。split 命令将采用某种方法实现此目的,以便可以使用 cat 命令轻松地重新创建文件。默认情况下,split 命令得到的文件的名称中有一个 ‘x’ 前缀和 ‘aa’、‘ab’、……‘ba’、‘bb’ 等后缀。可以使用选项来更改这些默认设置。还可以控制输出文件的大小,以及结果文件包含整行还是仅包含多少个字节。

清单 7 演示了对两个为输出文件使用不同前缀的文本文件的拆分。我们将 text1 拆分为包含至多两行的文件,将 text2 拆分为包含至多 18 字节的文件。然后使用 cat 单独显示一些数据部分,并使用 globbing 显示完整文件,这已在介绍基本文件和目录管理(可在 系列路线图 中看到)的教程中介绍。

清单 7. 使用 split 和 cat 进行拆分和重新组合
ian@Z61t-u14:~/lpi103-2$ split-l 2 text1
ian@Z61t-u14:~/lpi103-2$ split -b 17 text2 y
ian@Z61t-u14:~/lpi103-2$ cat yaa
9	plum
3	banana
1ian@Z61t-u14:~/lpi103-2$ cat yab
0	apple
ian@Z61t-u14:~/lpi103-2$ cat y* x*
9	plum
3	banana
10	apple
1 apple
2 pear
3 banana

请注意,拆分名为 yaa 的文件不会以换行符结束,所以我们的提示符是使用 cat 显示它之后的偏移。

Wc、head 和 tail

Cat 显示整个文件。这适合像我们的示例文件这样的小文件,但您可能拥有大型文件。您可能希望先使用 wcWord Count,字数统计)命令来查看文件有多大。wc 命令显示了文件中的行数、字数和字节数。您还可以使用 ls -l 获得字节数。清单 8 显示了我们的两个文本文件的长格式目录清单,以及来自 wc 的输出。

清单 8. 对文本文件使用 wc
ian@Z61t-u14:~/lpi103-2$ ls -l text*
-rw-rw-r-- 1 ian ian 24 Jun  8 13:26 text1
-rw-rw-r-- 1 ian ian 25 Jun  8 13:36 text2
ian@Z61t-u14:~/lpi103-2$ wc text*
 3  6 24 text1
 3  6 25 text2
 6 12 49 total

可以使用一些选项来控制来自 wc 的输出,或者显示最大行长度等其他信息。请参阅手册页了解有关的细节。

可以使用两个命令来显示文件的第一部分 (head) 或最后一部分 (tail)。这些命令是 headtail。可以使用它们作为过滤器,还可以接受文件名作为参数。默认情况下,它们会显示文件或流的前(或后)10 行。清单 9 使用 dmesg 命令显示启动消息,结合使用 wctailhead,您会发现有 791 条消息,显示这些消息中的最后 10 条,最后显示从倒数第 15 条开始的 6 条消息。

清单 9. 使用 wc、head 和 tail 显示启动消息
ian@Z61t-u14:~/lpi103-2$ dmesg|wc
   1100    9541   74856
ian@Z61t-u14:~/lpi103-2$ dmesg | tail
[ 8334.796068] wlan0: direct probe to 00:02:6f:ff:39:0c (try 2/3)
[ 8335.000081] wlan0: direct probe to 00:02:6f:ff:39:0c (try 3/3)
[ 8335.204065] wlan0: authentication with 00:02:6f:ff:39:0c timed out
[ 8351.649200] wlan0: authenticate with 6c:b0:ce:fb:43:02
[ 8351.658626] wlan0: send auth to 6c:b0:ce:fb:43:02 (try 1/3)
[ 8351.661222] wlan0: authenticated
[ 8351.664069] wlan0: associate with 6c:b0:ce:fb:43:02 (try 1/3)
[ 8351.678348] wlan0: RX AssocResp from 6c:b0:ce:fb:43:02 (capab=0x431 status=0 aid=3)
[ 8351.678479] wlan0: associated
[12050.026812] perf interrupt took too long (2502 > 2500), lowering kernel.perf_event_max_sample_ra
te to 50000
ian@Z61t-u14:~/lpi103-2$ dmesg | tail -n15 | head -n 6
[ 8324.132080] wlan0: direct probe to 6c:b0:ce:fb:43:02 (try 2/3)
[ 8324.336072] wlan0: direct probe to 6c:b0:ce:fb:43:02 (try 3/3)
[ 8324.540069] wlan0: authentication with 6c:b0:ce:fb:43:02 timed out
[ 8334.589068] wlan0: authenticate with 00:02:6f:ff:39:0c
[ 8334.594323] wlan0: direct probe to 00:02:6f:ff:39:0c (try 1/3)
[ 8334.796068] wlan0: direct probe to 00:02:6f:ff:39:0c (try 2/3)

tail 的另一个常见用法是使用 -f 选项关注 某个文件,通常使用行数 1。您在有一个在文件中生成输出的后台进程,而且希望签入并查看它的运行情况时,可以使用此选项。在这种模式下,tail 将运行到您(使用 Ctrl-c)取消使用它,并显示它们写入该文件的行。

Expand、unexpand 和 tr

我们创建 text1 和 text2 文件时,创建了包含制表符的 text2。有时,您可能希望将制表符切换为空格或将空格切换为制表符。expandunexpand 命令将执行此操作。两个命令的 -t 选项可用于设置制表符终点。只使用一个值就可以设置以该间隔重复的制表符。清单 10取消对齐 text2 中的文本的 expandunexpand

清单 10. 使用 expand 和 unexpand
ian@Z61t-u14:~/lpi103-2$ expand -t 1 text2
9 plum
3 banana
10 apple
ian@Z61t-u14:~/lpi103-2$ expand -t8 text2|unexpand -a -t2|expand -t3
9           plum
3           banana
10       apple
ian@Z61t-u14:~/lpi103-2$ cat text1 |tr ' ' '\t'|cat - text2
1	apple
2	pear
3	banana
9	plum
3	banana
10	apple

遗憾的是,不能使用 unexpand 将 text1 中的空格替换为制表符,因为 unexpand 需要至少两个空格才能转换为制表符。但您可以使用 tr 命令,它将一个字符集 (set1) 中的字符转换为另一个字符集 (set2) 中的相应字符。清单 11 显示了如何使用 tr 将空格转换为制表符。因为 tr 只是一个过滤器,所以使用 cat 命令生成它的输入。此示例还演示了使用 - 表示 cat 的标准输入,以便可以将 tr 的输出和 text2 文件串联起来。

清单 11. 使用 tr
ian@Z61t-u14:~/lpi103-2$ cat text1 |tr ' ' '\t'|cat - text2
1	apple
2	pear
3	banana
9	plum
3	banana
10	apple

如果不确定最后两个示例中发生的结果,可以尝试使用 od 依次终止管道的每个阶段;例如
cat text1 |tr ' ' '\t' | od -tc

Pr、nl 和 fmt

pr 命令用于格式化文件以便打印它。默认的页眉包含文件名、文件创建日期和时间,以及页码和两行空白页脚。从多个文件或标准输入流创建输出时,使用当前日期和时间来代替文件名和创建日期。您可以通过选项控制格式的许多方面来分列并排打印文件。跟平常一样,请参阅手册页了解有关的细节。

nl 命令将对行进行编号,这在打印文件时可能很方便。您还可以使用 cat 命令的 -n 选项来对行进行编号。清单 12 显示了如何打印我们的文本文件,然后如何对 text2 进行编号,以及如何将它与 text1 并排打印。

清单 12. 编号和格式化以便打印
ian@Z61t-u14:~/lpi103-2$ pr text1 | head


2015-06-08 13:26                      text1                       Page 1


1 apple
2 pear
3 banana


ian@Z61t-u14:~/lpi103-2$ nl text2 | pr -m - text1 | head


2015-06-08 16:10                                                  Page 1


     1	9	plum			    1 apple
     2	3	banana			    2 pear
     3	10	apple			    3 banana

另一个格式化文本的有用命令是 fmt 命令,该命令将格式化文本,使其适合相应的边距。可以合并多个短行和拆分长行。在 清单 13 中,我们使用 !#:* 历史功能的变体创建包含一个长文本行的 text3,避免将您的句子键入 4 次。我们还创建了每行一个单词的 text4。然后使用 cat 显示它们未格式化的形式,包含一个显示的 '$' 字符来表示行尾。最后,我们使用 fmt 将它们格式化为最大宽度为 60 个字符。同样地,请参阅手册页来了解其他选项的细节。

清单 13. 格式化为最大行长度
ian@Z61t-u14:~/lpi103-2$ echo "This is a sentence. " !#:* !#:1->text3
echo "This is a sentence. " "This is a sentence. " "This is a sentence. ">text3
ian@Z61t-u14:~/lpi103-2$ echo -e "This\nis\nanother\nsentence.">text4
ian@Z61t-u14:~/lpi103-2$ cat -et text3 text4
This is a sentence.  This is a sentence.  This is a sentence. $
This$
is$
another$
sentence.$
ian@Z61t-u14:~/lpi103-2$ fmt -w 60 text3 text4
This is a sentence.  This is a sentence.  This is a
sentence.
This is another sentence.

Sort 和 uniq

sort 命令使用针对系统的语言环境 (LC_COLLATE) 的整理顺序对输入进行排序。sort 命令还可以合并已排序的文件,并检查文件是否已排序。

清单 14 演示了在将 text1 中的空白转换为制表符后使用 sort 命令对我们的两个文本文件进行排序。因为排序顺序是按字符进行排序,所以您可能对结果感到奇怪。幸运的是,sort 命令可按数字值或字符值进行排序。您可以为整条记录或每个字段 指定此选项。除非指定不同的字段分隔符,否则字段将用空白或制表符进行分隔。清单 14 中的第二个示例展示了按数字排序第一个字段和按整理顺序(字母顺序)排序第二个字段。它还演示了如何使用 -u 选项消除所有重复行,仅保留唯一的行。

清单 14. 按字符和数字排序
ian@Z61t-u14:~/lpi103-2$ cat text1 | tr ' ' '\t' | sort - text2
10	apple
1	apple
2	pear
3	banana
3	banana
9	plum
ian@Z61t-u14:~/lpi103-2$ cat text1|tr ' ' '\t'|sort -u -k1n -k2 - text2
1	apple
2	pear
3	banana
9	plum
10	apple

请注意,我们仍有两个行包含水果 “apple”,因为在所有两个排序键(在我们的例子中为 k1n 和 k2)上执行了唯一性测试。考虑如何在上面的管道中修改或添加步骤,以消除第二次出现的 ‘apple’。

另一个名为 uniq 的命令为我们提供了控制如何消除重复行的另一种方法。uniq 命令通常用于已排序的文件,可以删除任何文件中连续的相同行,无论该文件是否已排序。uniq 命令还可以忽略一些字段。清单 15 使用了第二个字段(水果名)对我们的两个文件排序,然后消除从第二个字段开始的相同行(也就是说,我们在使用 uniq 执行测试时跳过第一个字段)。

清单 15. 使用 uniq
ian@Z61t-u14:~/lpi103-2$ cat text1|tr ' ' '\t'|sort -k2 - text2|uniq -f1
10	apple
3	banana
2	pear
9	plum

我们按整理顺序进行排序,所以 uniq 提供了 “10 apple” 行而不是 “1 apple”。尝试在键字段 1 上添加数字排序,查看排序有何变化。

Cut、paste 和 join

现在让我们看看另外 3 个处理文本数据中的字段的命令。这些命令对处理表列数据特别有用。第一个是 cut 命令,它从文本文件中提取字段。默认的字段分隔符为制表符。清单 16 使用 cut 分隔 text2 的两列,然后使用一个空格作为输出分隔符,这是将每行中的制表符转换为空格的外来方式。

清单 16. 使用 cut
ian@Z61t-u14:~/lpi103-2$ cut -f1-2 --output-delimiter=' ' text2
9 plum
3 banana
10 apple

paste 命令并排粘贴来自两个或多个文件的行,类似于 pr 命令使用其 -m 选项合并文件的方式。清单 17 显示了粘贴两个文本文件的结果。

清单 17. 粘贴文件
ian@Z61t-u14:~/lpi103-2$ paste text1 text2
1 apple	9	plum
2 pear	3	banana
3 banana	10	apple

这些示例展示了简单的粘贴,但 paste 可通过其他许多方法粘贴来自一个或多个文件的数据。请参阅手册页了解有关的细节。

我们的最后一个字段处理命令是 join,它根据一个匹配字段来合并文件。这些文件会按照这个合并字段进行排序。因为 text2 未按照数字顺序进行排序,所以我们可以对它进行排序,然后 join 将合并两个具有匹配的合并字段(在本例中为具有值 3 的字段)的行。

清单 18. 合并具有合并字段的行
ian@Z61t-u14:~/lpi103-2$ sort -n text2|join -j 1 text1 -
3 banana banana
join: -:3: is not sorted: 10	apple

哪里出错了?还记得 Sort 和 uniq 部分介绍的字符和数字排序的内容吗?join 依据语言环境的整理顺序而在匹配的字符上执行。它不会在数字字段上执行,除非这些字段都具有相同的长度。

我们使用了 -j 1 选项来合并来自每个文件的字段 1。用于合并的字段可为每个文件单独指定。举例而言,您可以根据来自一个文件的字段 3 和来自另一个文件的字段 10 来执行合并。

让我们按照第二个字段(水果名)对 text 1 进行排序,然后将空格替换为制表符,通过这种方式创建一个新文件 text5。然后,如果我们按第二个字段对 text2 进行排序,并使用每个文件的第二个字段作为合并字段来将该文件与 text5 合并,我们会有两个匹配值(apple 和 banana)。清单 19 演示了此合并。

清单 19. 合并具有合并字段的行
ian@Z61t-u14:~/lpi103-2$ sort -k2 text1|tr ' ' '\t'>text5
ian@Z61t-u14:~/lpi103-2$ sort -k2 text2 | join -1 2 -2 2 text5 -
apple 1 10
banana 3 3

Sed

Sed 表示 stream editor(流编辑器)。一些 developerWorks 教程以及许多图书和图书章节介绍了 sed(请参见 参考资料)。Sed 非常强大,它可以完成您能想到的任何任务。这个简短介绍应会激起您对 sed 的兴趣,但内容并不完整或丰富。

与我们目前介绍的许多文本命令一样,sed 可用作过滤器或从文件获取输入。输出是标准输出流。Sed 将来自输入的行加载到模式空间 中,对模式空间的内容应用 sed 编辑命令,然后将模式空间写入到标准输出。Sed 可将多个行组合在模式空间中,它可以写入到一个文件,仅写入选定的输出,或者完全不写入任何内容。

Sed 使用正则表达式语法选择性地搜索和替换模式空间中的文本,并控制一组编辑命令应处理哪些文本行。正则表达式已在介绍使用正则表达式搜索文本文件 的教程(可以在 系列路线图 中找到)中进行了更全面地介绍。保持缓冲区 为文本提供了临时存储。保持缓冲区可取代模式空间,添加到模式空间,或者与模式空间交换。Sed 拥有一组有限的命令,但将这些命令与正则表达式语法和保持缓冲区结合使用,可以实现一些神奇的功能。一组 sed 命令通常称为sed 脚本

清单 20 显示了 3 个简单的 sed 脚本。在第一个脚本中,我们使用 s(替换)命令用大写字母 ‘A’ 替换每行上的小写字母 ‘a’。此示例仅替换第一个 ‘a’,所以在第二个示例中,我们添加 ‘g’(表示全局)标志来让 sed 更换所有出现的字符。在第三个脚本中,我们引入了 d(删除)命令来删除一行。在我们的例子中,我们使用地址 2 来表示仅应删除第 2 行。我们使用分号 (;) 分隔命令,使用第二个脚本中使用的相同的全局替换来将 ‘a’ 替换为 ‘A’。

清单 20. 开始运行 sed 脚本
ian@Z61t-u14:~/lpi103-2$ sed 's/a/A/' text1
1 Apple
2 peAr
3 bAnana
ian@Z61t-u14:~/lpi103-2$ sed 's/a/A/g' text1
1 Apple
2 peAr
3 bAnAnA
ian@Z61t-u14:~/lpi103-2$ sed '2d;$s/a/A/g' text1
1 apple
3 bAnAnA

除了处理单行之外,sed 还可以处理许多行。范围的起点和终点由逗号 (,) 分隔且可以指定行号、正则表达式或美元符号 ($) 来表示文件结束。给定一个地址或地址范围,您可以将多个命令放在花括号 { 和 } 中来分组,让这些命令仅处理该范围所选定的行。清单 21 演示了仅对我们的文件的最后两行应用全局替换的两种方式。它还演示了如何使用 -e 选项向脚本添加多个命令。

清单 21. Sed 地址
ian@Z61t-u14:~/lpi103-2$ sed -e '2,${' -e 's/a/A/g' -e '}' text1
1 apple
2 peAr
3 bAnAnA
ian@Z61t-u14:~/lpi103-2$ sed -e '/pear/,/bana/{' -e 's/a/A/g' -e '}' text1
1 apple
2 peAr
3 bAnAnA

Sed 脚本也可以存储在文件中。事实上,您可能希望对顺序使用的脚本这么做。还记得我们之前使用了 tr 命令来将 text1 中的空白更改为制表符吗?现在让我们使用存储在文件中的 sed 脚本来实现此目的。我们将使用 echo 命令创建该文件。结果如 清单 22 所示。

清单 22. 一个 sed 单行脚本
ian@Z61t-u14:~/lpi103-2$ echo -e "s/ /\t/g">sedtab
ian@Z61t-u14:~/lpi103-2$ cat sedtab
s/ /	/g
ian@Z61t-u14:~/lpi103-2$ sed -f sedtab text1
1	apple
2	pear
3	banana

有许多便捷的 sed 单行脚本,比如 清单 22。请参见 参考资料,获取一些脚本的链接。

我们的最后一个 sed 示例使用了 = 命令打印行号,然后再次通过 sed 过滤输出结果,以便模仿使用 nl 命令对行编号的效果。清单 23 使用 = 打印行号,然后使用 N 命令将第二个输入行读入到模式空间中,最后删除模式空间中的两行之间的换行符 (\n)。

清单 23. 使用 sed 对行编号
ian@Z61t-u14:~/lpi103-2$ sed '=' text2
1
9	plum
2
3	banana
3
10	apple
ian@Z61t-u14:~/lpi103-2$ sed '=' text2|sed 'N;s/\n//'
19	plum
23	banana
310	apple

这不太符合我们的期望!我们真正想要的是让行号在一列中对齐,在来自该文件的行之前有一些空格。在 清单 24 中,我们输入了多行命令(请注意 > 辅助提示符)。请学习该示例并参阅下面的解释。

清单 24. 使用 sed 对行编号 - 第二次
ian@Z61t-u14:~/lpi103-2$ cat text1 text2 text1 text2>text6
ian@Z61t-u14:~/lpi103-2$ ht=$(echo -en "\t")
ian@Z61t-u14:~/lpi103-2$ sed '=' text6|sed "N
> s/^/      /
> s/^.*\(......\)\n/\1$ht/"
     1	1 apple
     2	2 pear
     3	3 banana
     4	9	plum
     5	3	banana
     6	10	apple
     7	1 apple
     8	2 pear
     9	3 banana
    10	9	plum
    11	3	banana
    12	10	apple

以下是我们采取的步骤:

  1. 首先使用 cat 根据我们的 text1 和 text2 文件的两个副本创建一个包含 12 行的文件。如果编号的位数相同,格式化列中的编号就没有什么有趣的地方。
  2. bash shell 使用制表符键来完成命令,所以拥有一个可在想要真正的制表符时使用的受控制表符会很方便。我们使用 echo 命令完成此任务,将字符保存在 shell 变量 ‘ht’ 中。
  3. 像之前一样创建一个流,其中的行号后面是数据行,通过 sed 的第二个副本过滤它。
  4. 将第二行读入到模式空间中
  5. 在模式空间开头(使用 ^ 表示)的行号上添加 6 个空格作为前缀。
  6. 然后将换行符前面的所有行替换为换行符前面的最后 6 个字符和一个制表符。这将对齐输出行的前 6 列中的行号。请注意,‘s’ 命令的左侧部分使用 ‘\(’ 和 ‘\)’ 来标记我们想在右侧部分使用的字符。在右侧部分,我们使用 \1 引用第一个具有此标记的集合(在本例中只有这个集合)。请注意,我们的命令包含在双引号 (") 之间,以便对 $ht 执行该替换。

第 4 版的 sed 包含 info 格式的文档并包含许多好的示例。更早的 3.02 版没有这些内容。GNU sed 将接受 sed --version 来显示版本。

分页器

使用 man 命令查看手册页时,您可能会注意到大部分手册页包含的内容都无法在一个屏幕上显示完。出于这个原因,man 命令也被称为手册分页器,因为它允许对该信息进行分页。man 的工作方式是,从压缩的手册文件提取请求的信息,将它格式化为一个输出字符流。然后,它使用一个分页器程序在终端上显示实际的流。典型的 Linux 系统可使用许多分页器程序,如果不喜欢默认分页器,还可以配置 man 来使用您最喜欢的分页器。

在早期的 UNIX 系统中,屏幕输出使用 more 分页器来分页。最初的 more 程序需要在显示信息之前读取完整的输入,而且只能向前翻页。这些是严重的限制,尤其在传输一个长期运行的程序的输出时,需要等待它完成之后才能看到输出。

less 分页器解决了 more 的两个主要限制(正如手册页所说的,“less 是 more 的反向操作”)。只要输出可用,它就会显示出来,并且允许向后翻页。

正如您所期望的,less 命令有一个手册页,而且它还有大量选项来控制自己的行为。通常的默认行为是使用 Page UpPage Down 键上滚或下滚页面。您还可以输入命令。最常见的搜索方法是在 / 后跟一个搜索字符串。可以使用 ? 代替 / 来向后搜索。可以使用 h 命令获得可用命令的摘要。清单 25 显示了在文件内移动的手册页帮助摘要。请注意,还有一个小节介绍了在文件中跳跃 - 例如跳到文件顶部或底部。

清单 25. 针对 less 中的移动操作的手册页帮助
                            MOVING

  e  ^E  j  ^N  CR  *  Forward  one line   (or N lines).
  y  ^Y  k  ^K  ^P  *  Backward one line   (or N lines).
  f  ^F  ^V  SPACE  *  Forward  one window (or N lines).
  b  ^B  ESC-v      *  Backward one window (or N lines).
  z                 *  Forward  one window (and set window to N).
  w                 *  Backward one window (and set window to N).
  ESC-SPACE         *  Forward  one window, but don't stop at end-of-file.
  d  ^D             *  Forward  one half-window (and set half-window to N).
  u  ^U             *  Backward one half-window (and set half-window to N).
  ESC-)  RightArrow *  Left  one half screen width (or N positions).
  ESC-(  LeftArrow  *  Right one half screen width (or N positions).
  F                    Forward forever; like "tail -f".
  r  ^R  ^L            Repaint screen.
  R                    Repaint screen, discarding buffered input.
        ---------------------------------------------------
        Default "window" is the screen height.
        Default "half-window" is half of the screen height.

清单 26 显示了的手册页帮助摘要

清单 26. 针对 less 中的搜索操作的手册页帮助
                          SEARCHING

  /pattern          *  Search forward for (N-th) matching line.
  ?pattern          *  Search backward for (N-th) matching line.
  n                 *  Repeat previous search (for N-th occurrence).
  N                 *  Repeat previous search in reverse direction.
  ESC-n             *  Repeat previous search, spanning files.
  ESC-N             *  Repeat previous search, reverse dir. & spanning files.
  ESC-u                Undo (toggle) search highlighting.
  &pattern          *  Display only matching lines
        ---------------------------------------------------
        A search pattern may be preceded by one or more of:
        ^N or !  Search for NON-matching lines.
        ^E or *  Search multiple files (pass thru END OF FILE).
        ^F or @  Start search at FIRST file (for /) or last file (for ?).
        ^K       Highlight matches, but don't move (KEEP position).
        ^R       Don't use REGULAR EXPRESSIONS.

moverless 分页器默认情况下会安装在大部分 Linux 系统上,lessman 命令的默认分页器。还存在其他分页器,包括 most,它允许在屏幕上以窗口形式查看多个文件。和平常一样,可以使用 maninfo 命令找到更多信息。本系列的另一篇教程将介绍如何安装默认情况下未安装在您系统上的包。


相关主题


评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Linux
ArticleID=1028419
ArticleTitle=学习 Linux,101: 文本流和过滤器
publish-date=01302016