第 16 章...
过滤器:入门与基本操作 (Filters: Introduction and Basic Operations)
- 命令与选项的变体
- 过滤器
- 你该自己编写过滤器吗?
- 解决问题的过程
- 最简单的过滤器: cat
- 增强过滤器的功能
- 最实用的过滤器一览
- 合并文件: cat
- 拆分文件: split
- 合并文件并倒序排列行: tac
- 颠倒字符顺序: rev
- 选取数据的开头或结尾若干行: head、tail
- 删除数据列: colrm
- 练习
在第 15 章中,我们讨论了 Unix 的设计理念如何催生了 许多程序,每一个程序都是为把一件事做好而设计的工具。 我们还谈到了如何重定向输入和输出, 以及如何创建把数据从一个程序传递给 下一个程序的管道。
在接下来四章(第 16、17、18、19 章)中,我们将继续 这个话题,认识一批非常实用的 Unix 程序,它们被称为 "过滤器"。(我很快会给出准确的定义。)把这些程序 与我们在第 15 章中讨论的技巧结合起来使用, 你就能构建灵活、定制化的解决方案, 去应对各种各样的问题。
我们先从一些普遍性的话题开始,帮助你理解过滤器 为何重要以及它们是如何使用的。然后再逐个讨论 最重要的那些 Unix 过滤器。尽管其中一些过滤器 彼此相关,但它们都是独立的工具, 不必按某个特定顺序来学习。 如果你想了解某个具体的过滤器,可以直接跳到 相应的小节。不过,如果你有时间,我更希望你 把这四章从头到尾按顺序读完,因为我会在行文中 逐步展开若干重要的概念。如果你想在开始之前 先看到过滤器的列表,可以看看图 16-1, 它就在本章后文。
在第 20 章中,我们会讨论一项极其重要的机制, 叫做正则表达式,它用来指定模式。正则表达式能 大幅提升过滤器的能力,所以你应该把接下来这四章 与第 20 章视为互补的内容。
命令与选项的变体
(Variations of Commands and Options)
第 16、17、18、19 章的目的是讨论基本的 Unix 过滤器。这些程序在大多数 Unix 和 Linux 版本中 都可用。如果你的系统上没有某个程序,那可能是 因为它默认没有安装,而你需要安装某个特定的 软件包。例如,在某些 Linux 发行版中,除非你 安装了 binutils(二进制文件工具)软件包, 否则你无法使用 strings 程序(第 19 章)。
如你所知,某个具体程序的细节在不同系统之间会有 差异。在本章中,我会针对每个命令描述其 GNU 版本。由于 Linux 和 FreeBSD 使用的都是 GNU 工具集(见第 2 章),如果你是 Linux 或 FreeBSD 用户,这四章中所讲的内容应该可以在你的 系统上原样使用。如果你使用的是其他类型的 Unix, 可能会有一些差异,但差别很小。
例如,本章后文我会讨论 cat 命令可以使用的 三个选项。如果你使用 Linux 或 FreeBSD,这些选项 会完全如我所演示的那样工作。如果你使用 Solaris, 其中有一个选项(-s)的含义则不同。
在讨论每个过滤器时,我会介绍该程序最重要的那些 选项。你要明白,大多数程序还会有我们不会涉及的 其他选项。事实上,几乎所有 GNU 工具都有大量 选项,远远超出你平时所需。
阅读本章时,请记住:每当你想了解某个程序,都可以 通过 man 命令访问联机手册,阅读你自己的 系统上的权威文档;如果你使用的是 GNU 工具集,还可以 用 info 命令访问 Info 系统。(这些在第 9 章 中都有讲解。)特别地,你可以用 man 命令列出 某个命令的全部可用选项。例如,要了解 cat 命令(本章就有讨论),你可以使用:
man cat
info cat
开始之前,我再提两点适用于 GNU 工具集(Linux 和 FreeBSD 所用)的内容。第一,正如我们在第 10 章 所讨论的,大多数 GNU 工具有两类选项。一类是短选项, 由一个连字符(减号)加上单个字符构成;另一类是长选项, 由两个连字符加上一个单词构成。在第 10 章中,我们 把它们称为"dash"和"dash-dash"选项,因为大多数人 都是这么称呼它们的。
一般而言,最重要的选项是那些短选项。多数情况下, 长选项要么是某个短选项的同义词,要么是你大概用不到的 更为冷僻的选项。因此,在本书中,我通常只讨论短选项。
不过,有一个长选项你应该记住。对于 GNU 工具集, 大多数命令都识别一个名为 --help 的选项。 你可以用它显示几乎任何命令的语法,包括该命令选项的 摘要。例如,要显示 cat 命令的语法和选项, 你可以使用:
cat --help
过滤器
(Filters)
在第 15 章中,你已经看到一系列程序如何被依次 使用以构成一条管道,情形有点像流水线。举个例子, 看下面这条命令,数据依次流经四个程序:cat、 grep、sort 和 less。
cat new old extra | grep Harley | sort | less
在这条管道中,我们先合并三个名为 new、 old 和 extra 的文件(用 cat), 提取出所有包含 Harley 的行(用 grep), 然后对这些结果排序(用 sort)。最后我们用 less 一次一屏地显示最终输出。
不必担心 cat、grep 和 sort 的 使用细节。我们后面会讲到。此刻我只希望你能体会到: 如果一个程序在设计时就考虑了可以在管道中使用, 它会多么有用。
我们把这样的程序称为过滤器。例如,cat、 grep 和 sort 都是过滤器。这类程序读取 数据,对数据做某种处理,然后写出结果。更准确地说, 凡是按行读取文本数据并按行写出文本数据的程序, 也就是从标准输入读取、向标准输出写出的程序, 都是过滤器。一般而言,大多数过滤器都是作为工具 设计的,只求把一件事做好。
有意思的是,管道中的第一个和最后一个程序并不一定 非要是过滤器。在我们的例子里,我们用 less 来 显示 sort 的输出。我们会在第 21 章详细 讨论 less。这里先告诉你:less 显示输出时, 允许你一次一屏地查看全部数据、向前向后滚动、 搜索特定的模式,等等。显然,less 并不是逐行 写入标准输出的,这意味着它不是过滤器。
同样,这条管道中的第一个命令 cat(用于合并 文件)并没有从标准输入读取。虽然 cat 可以 当作过滤器来用,但在这里它是从文件读取输入的, 而不是从标准输入。所以在我们的例子中,cat 此时并不算过滤器。
— 提示 —
当你发现自己要反复使用某条特定的管道时,可以在 环境文件中定义一个别名,把它固定下来(见 第 14 章)。这样你就能随时使用这条管道, 而不必每次都把它敲一遍。
你该自己编写过滤器吗?
(Should You Create Your Own Filters?)
如果你是程序员,自己写过滤器并不难。你要做的只是 编写一个程序或 Shell 脚本,使用标准输入输出, 按行读写文本数据。任何这样做的程序都是过滤器, 因此可以在管道中使用。
不过,在你兴冲冲地去设计自己的程序之前,请允许我 提醒你:每一套 Unix 和 Linux 系统都自带数百个程序, 其中很多都是过滤器。事实上,在过去三十五年里, 历史上最聪明的一批程序员一直在创造并打磨 Unix 过滤器。
这就是说,如果你想到了一个新过滤器的绝妙点子, 很可能很久以前别人已经有了同样的想法,而且这个 过滤器早已存在。事实上,本章我们要讨论的大多数工具 都已经问世三十多年了!所以,当你遇到问题时,先弄清 已经有哪些可用的工具,再决定是否花时间去写自己的 程序,这才是明智之举。正是出于这个原因,我才花了 那么多篇幅教你使用联机手册和 Info 系统(第 9 章), 以及 man、whatis、apropos 和 info 命令(同样在第 9 章)。
善用 Unix 的诀窍,并不一定在于能写程序造出新工具, 尽管那当然很有用。对大多数人来说,善用 Unix 意味着 能够把已有的工具组合起来解决问题。
解决问题的过程
(The Problem Solving Process)
如果你看一位 Unix 老手用过滤器搭建管道,那手法看起来 颇为神秘。仿佛凭空而来,他或她就恰好知道该用哪些 过滤器,也知道怎样把它们恰到好处地组合起来。终有一日 你也能做到。这需要的是知识和练习。不过在起初阶段, 把整个过程拆解成一系列步骤会有帮助,那我们花一点 时间来做这件事。
你的目标是:想办法把若干过滤器组合成一条单独的管道 来解决眼前的问题。必要时,你可以使用多条命令行, 甚至可以用一个简单的 Shell 脚本,里面写上一串命令。 然而,最精明的 Unix 用户用一个命令行就能解决大多数 问题,所以请以这一点作为你的起点目标。
眼下,在你真正学会使用一些 Unix 过滤器之前,你能做的 并不多。所以阅读本节时,请把它当作"一些将来你会觉得 有道理的一般性建议"。我接下来要讲的是一张路线图, 告诉你即将走向何方。你只需领会其中的总体思路, 以后卡住的时候,再回来把本节重读一遍,就能解开困惑。
那么:你有一个问题,想用过滤器和管道来解决。该怎么做? 步骤如下。
1. 界定问题。
先思考。找个安静的地方,闭上眼睛,想想如何把你的问题 拆分成若干部分,每一部分都可以由一个独立的程序来完成。 在这个阶段,你还不需要知道将用哪些工具来执行这些任务, 你唯一需要做的就是思考。
有经验的 Unix 用户思考问题时,会在脑中反复推敲, 从不同的角度看它,直到找出一个看起来行得通的思路。 然后他们去寻找干这件事的工具,接着动手试验, 看看效果如何。如果你观察他们工作,你会发现他们 从不灰心丧气。(请花一点时间想想这句话。)
2. 选择你的工具。
Unix 程序有数百个之多,其中许多是过滤器;而要用好 Unix,你需要知道对于你恰好碰到的问题,哪些程序最合适。 这听起来当然近乎不可能。你怎么可能记住数百个程序各自的 功能,更不用说记住细节了?
实际上你会发现,大多数 Unix 问题都可以从一个大约包含 三十个程序的小型工具箱中挑选过滤器来解决。多年来, 这些程序被证明是最通用、最有用的,也正是接下来四章 我们要讨论的程序。作为参考,图 16-1(本章后文) 列出了这些重要的过滤器。
3. 与他人交流。
一旦你思考过如何界定问题,并且对可能用到哪些工具心里 有了谱,就去找可以请教的人。确实,你应当先读手册再 求助(见第 9 章);但传统上,Unix 一直是以口口 相传的方式学习的。要成长为一名成熟的 Unix 用户, 你必须看看其他更有经验的人是如何解决问题的。
4. 挑选选项。
当你研究过问题并选好工具之后,应该花一点时间查看 联机手册中的文档(第 9 章)。对你打算使用的 每个程序都要这样做。你的目标是了解各个选项, 找出与你正要做的事情相关的那些。
情况总是这样:你可以放心地忽略大多数选项。然而, 至少把选项说明浏览一遍永远是明智的做法,万一这个 问题恰好需要某个选项呢。否则,你可能会因为不知道 某个选项的存在,而白白做许多额外的工作。我认识的 那些最精明、最博学的人,每天都要翻好几回联机手册。
— 提示 —
在运用重定向、过滤器和管道解决问题这件事上, 最重要的三种能力是:思考、RTFM(*),以及向他人请教。
* 脚注
术语表中解释了 RTFM,第 9 章也有说明。
最简单的过滤器:cat
(The Simplest Possible Filter: cat)
过滤器逐行从标准输入读取数据,做些处理,然后逐行把 结果写到标准输出。那么,最简单的过滤器会是什么样子? 就是什么都不做的那个。
这个过滤器的名字叫 cat(你一会儿就会明白为什么), 它所做的只是把数据从标准输入复制到标准输出,既不做什么 特别的事,也不以任何方式改动数据。
下面是一个你可以亲手试试的简单例子。输入命令:
cat
cat 程序会启动并等待来自标准输入的数据。由于 标准输入默认就是键盘,cat 实际上是在等你敲些 什么。
你想敲什么就敲什么。每敲完一行,按下 <Return> 键。每次你按 <Return>,刚敲下的那一行就会被送给 cat,而它会把它复制到标准输出,默认就是你的 屏幕。结果是:你敲的每一行都会显示两遍,一遍是你 敲的时候,另一遍是 cat 打出来的。例如:
this is line 1
this is line 1
this is line 2
this is line 2
输完之后,按下 ^D(<Ctrl-D>),也就是 eof 键。这是告诉 Unix 没有更多输入了(见 第 7 章)。于是 cat 命令结束,你回到 Shell 提示符。
到这里,你大概已经在问:一个什么都不做的过滤器有什么用? 其实它有好几种用途,而且每一种都很重要。
由于 cat 什么都不做,把它放在管道里是没有意义的 (请花点时间想一想,直到你想通为止)。但是,把 cat 单独与输入输出重定向结合起来用时,它就很 顺手了。当管道开头需要合并多个文件时,它也很有用。 下面举几个例子。
cat 的第一个用途,是与重定向配合,快速创建一个 小文件。考虑下面这条命令:
cat > data
标准输入(默认)是键盘,而标准输出被重定向到了一个 名为 data 的文件。于是,你每按一次 <Return> 键,刚敲下的那一行就被直接复制进这个文件。你想敲多少 行都可以,敲完之后按 ^D 告诉 cat 没有更多 数据了。
如果文件 data 尚不存在,Unix 会替你创建它。如果 文件已经存在,它的内容会被替换掉。这样,你就可以用 cat 新建文件,或者替换已有文件的内容。当只想创建 或替换一个小文件时,老用户经常这么做。
我之所以强调"小文件",是因为你一按 <Return>, 刚敲的那一行就已经复制到标准输出去了。如果你想修改 那一行,就得停止 cat、重新启动,然后把所有内容 重敲一遍。
我觉得,用这种方式操作 cat,是快速创建或替换 小文件的极好办法,比如最多四五行。比起启动 vi 或 Emacs 这样的文本编辑器、敲入文本、再退出编辑器, 用 cat 更快(也更有意思)。当然,这样很容易出错, 所以如果我要敲的行数超过 5 行,我会用编辑器,因为它 允许我边敲边改。
cat 的第二个用途,是把少量几行文字追加到一个 已有文件的末尾。为此你要用 >> 来重定向标准输出, 例如:
cat >> data
现在,你敲的一切都会被追加到文件 data 的末尾。 (正如我在第 15 章解释的,用 >> 重定向输出时, Shell 会把输出追加上去。)
cat 的第三个用途是显示一个短文件。只要把标准 输入重定向到你想显示的那个文件即可。例如:
cat < data
在这种情况下,输入来自文件 data,输出则去往 屏幕(默认)。换句话说,你刚刚显示了文件 data。 当然,如果文件比屏幕的高度还长,有些行在你来得及 阅读之前就已经滚出屏幕了。这种情况就不该用 cat 来显示文件,而应该用 less(第 21 章) 一次一屏地显示。
cat 的第四个用途是显示任何文件的最后部分。 假设你用上一条命令显示名为 data 的文件。如果 data 是个能装进屏幕的短文件,那就皆大欢喜。 但如果 data 比屏幕还长,除最后一段之外的内容 都会滚出屏幕。通常这一切发生得很快,最后留在屏幕上的 就是文件的末尾部分 — 正好是能显示满一屏的内容 — 而这恰恰是你想要的。
如果你想亲手试试,可以用 cat 显示我在 第 6 章讨论过的某个配置文件。例如,试试这条命令:
cat < /etc/profile
注意,当你显示一个长文件时,cat 会让你正好停在 文件的最后部分。
作为一种便利,如果你省略 < 字符,cat 会直接从文件读取。(下一节我们再细说。)所以,如果你想 拿第 6 章里的那些配置文件做实验,可以使用的命令是:
cat /boot/grub/menu.lst
cat /etc/hosts
cat /etc/inittab
cat /etc/passwd
cat /etc/profile
cat /etc/samba/smb.conf
(说明:1. 如果你用的不是 Linux,你的系统可能没有以上 全部文件。2. 在大多数系统上,你需要是超级用户才能显示 menu.lst 文件。)
试用这些命令时你会发现:如果文件很短,cat 会很 快把它完整地展示给你;如果文件很长,大部分内容滚过得 太快,你根本来不及读。正如我们所讨论的,你看到的是 文件的最后部分(能占满一屏的那些)。
本章后文我们还会认识另一个程序,叫 tail,它可以 快速显示文件的末尾部分。多数时候,tail 比 cat 更合适。但在某些情况下,cat 其实是更好 的选择。这是因为 tail 显示的是你指定的行数, 默认是 10 行;而 cat 显示的是能占满你屏幕的 那么多行。要明白我的意思,可以拿上面列出的几个文件 试试 tail。例如:
tail /etc/profile
继续往下说,cat 的第五个用途是同时重定向标准 输入和标准输出来复制一个文件。例如,要把文件 data 复制到另一个名为 newdata 的文件, 输入:
cat < data > newdata
当然,Unix 有更适合复制文件的命令,它叫 cp, 我们在第 25 章会讲到。不过,知道 cat 在 需要时也能胜任这件事,是很有意思的。
cat 还能做更多的事,我们在下一节就会讲到。在那 之前,先花一点时间想想一件真正了不起的事情。我们从 最简单的过滤器 cat 开始,按定义,它什么都不做。 然而,把 cat 与输入输出重定向结合起来用,我们 就能让它精神抖擞地变出各种戏法。(这些都汇总在本章 后文的图 16-2 中。)
这样把 cat 从头到尾操练一遍,恰好为我们展示了 Unix 的精妙之处。一个看似简单的概念 — 数据应当从 标准输入流向标准输出 — 竟然以许多出人意料的方式 结出果实。看看一个什么都不做的过滤器能让我们做多少事, 再想象一下后面还有什么!
— 提示 —
Unix 的魅力之一,在于某个瞬间你忽然豁然开朗, 自言自语道:"原来如此,所以他们是那样做的。"
增强过滤器的功能
(Increasing the Power of Filters)
只要对过滤器做一处重要改动,就能极大提升它的实用性。 这个改动就是:可以指定一个或多个输入文件的文件名。
如你所知,过滤器的严格定义要求它从标准输入读取数据。 如果你想从文件读取数据,就必须把标准输入从那个文件 重定向过来,例如:
cat < data
那么,如果我们还能把文件名作为参数来指定,从那个文件 读取呢?例如:
cat data
cat 正是如此,后两条命令是等价的。于是,想快速 显示一个短文件,你只要敲 cat 再跟上文件名就行, 就像最后这个例子那样。有经验的 Unix 用户常常这样使用 cat,把它当作显示短文件的快捷办法。(更长的文件 你应当用 less>,见第 21 章。)
起初,这样一点小改动 — 省略掉 < 字符 — 看起来无足轻重,其实不然。我们确实让命令行更简单了, 这很方便,但也有代价。cat 程序本身现在必须更 复杂了:它不仅得能从标准输入读取,还必须能从任何 文件读取。而且,通过扩展 cat 的能力,我们失去了 纯粹过滤器的一部分优雅与简洁。
尽管如此,许多过滤器都以这种方式被扩展,这并不是为了 便于读取单个文件,而是为了让读取多个文件成为可能。 例如,下面是 cat 命令语法的简化版本:
cat [file...]
其中 file 是 cat 读取输入的文件名。
注意 file 参数后面的三个点。这意味着你可以指定 多个文件名。(关于命令语法的说明见第 10 章。)
于是 — 这正是要点所在 — 在扩展 cat 使其能 从任何文件读取的同时,我们也让它能从多个文件读取。 当我们指定多个输入文件时,cat 会依次读完每个 文件的全部数据。它一边读,一边把每一行文本按遇到的 顺序写到标准输出。这意味着我们可以用 cat 合并 任意多个文件的内容。
这是一个非常重要的概念,所以请花点时间仔细看看下面 几个例子:
cat name address phone
cat name address phone > info
cat name address phone | sort
第一个例子把多个文件(name、address 和 phone)的内容合并起来显示在你的屏幕上;第二个 例子合并同样的文件,把数据写到另一个文件(info) 里;第三个例子把数据通过管道交给一个程序 (sort)做进一步处理。
如我所说,许多其他过滤器(不只是 cat)也能从多个 文件读取输入。从技术上讲,这并非必要。如果我们想对来自 多个文件的数据做处理,可以用 cat 把数据收集起来, 再通过管道交给想要的任意过滤器。例如,假设我们要合并 三个文件的数据然后排序。sort 完全不必具备读取 多个文件的能力。我们要做的只是用 cat 合并数据, 再把它通过管道交给 sort:
cat name address phone | sort
从某个角度看,这样做很有吸引力。把 cat 扩展为 既能读文件又能读标准输入,使我们损失了整体设计中的一部分 优雅。然而,用 cat 去喂养其他过滤器,我们至少能 保留那些过滤器的纯粹性。
不过,就像生活中的许多事情一样,实用性最终战胜了优美与 纯粹。每次想把这类数据送给过滤器时都得用 cat 合并文件,实在太麻烦了。因此,大多数过滤器都允许我们把 多个文件名作为参数指定。
例如,下面三条命令都会对来自多个文件的数据排序。第一条 把输出显示在你的屏幕上;第二条把输出保存到一个文件; 第三条把输出通过管道交给另一个程序做进一步处理。 (细节暂时不必在意。)
sort name address phone
sort name address phone > info
sort name address phone | grep Harley
到这里,我想请你思考一个带有哲学意味的问题。按定义, 过滤器必须从标准输入读取数据。这是否意味着一个能从文件 读取数据的程序,其实算不上真正的过滤器?
有两种回答,两种都说得通。第一,我们可以认为:当 cat 或 sort 这样的程序从标准输入读取时, 它表现得像个过滤器;而当它从文件读取时,它就不算在充当 过滤器。这种做法维护了体系的纯粹性。然而,它也意味着 许多程序到底是不是过滤器,取决于它们怎样被使用。
另一种做法是:放宽过滤器的定义,允许它从标准输入或从 文件读取。这个定义很实用,但牺牲了最初设计的部分美感。
最实用的过滤器一览
(A List of the Most Useful Filters)
到这里,我们已经讨论了与过滤器有关的基本概念。本章剩下 的部分 — 以及第 17、18、19 章 — 我会一个接一个地 介绍各种各样的过滤器。作为预告,图 16-1 列出了我认为 最有用的 Unix 过滤器。
无论你使用哪种 Unix 或 Linux,你都会发现这四章中的大部分 内容在你的系统上都能工作。这是因为我们要讲的这些过滤器的 基本细节在不同系统之间是一致的。事实上,这些过滤器中的 大多数三十多年来一直是以同样的方式工作的!
不过,在我们继续之前,请允许我提醒你:任何时候,你都可以 用 man 命令显示某个程序的手册页,查阅关于它在 你的系统上如何工作的权威参考。如果你使用的是 GNU 工具集 — Linux 和 FreeBSD 都是如此 — (见第 2 章),你可以用 info 命令访问 Info 系统。例如:
man cat
info cat
对于大多数 GNU 工具,你可以用 --help 选项显示 命令的语法及其选项摘要。例如:
cat --help
关于如何使用 man 和 info,见第 9 章。 关于语法的讨论,见第 10 章。
01">图 16-1:最有用的 Unix 过滤器(Figure 16-1: The Most Useful Unix Filters)
这张表列出了最重要的 Unix 过滤器,它们中的大多数已有 三十多年的历史。用表中这些过滤器,你可以解决许多不同 类型的问题。多数情况下,你只需要一个过滤器;很少会 需要四个以上。
awk 和 perl 是复杂的编程语言,你可以用它们 编写程序,在管道中充当过滤器。要了解更多信息,请先看 联机手册(man awk、man perl),然后到万维网上 查找,那里有海量资料。
| 过滤器 | 章 | 另见 | 用途 |
|---|---|---|---|
| awk | — | perl | 编程语言:处理文本 |
| cat | 16 | rev、split、tac | 合并文件;把标准输入复制到标准输出 |
| colrm | 16 | cut、join、paste | 删除指定的数据列 |
| comm | 17 | cmp、diff、sdiff | 比较两个已排序的文件,显示差异 |
| cmp | 17 | comm、diff、sdiff | 比较两个文件 |
| cut | 17 | colrm、join、paste | 提取指定的数据列/字段 |
| diff | 17 | cmp、comm、sdiff | 比较两个文件,显示差异 |
| expand | 18 | unexpand | 把制表符转换成空格 |
| fold | 18 | fmt、pr | 把长行折成短行 |
| fmt | 18 | fold、pr | 重排段落,使其看起来美观 |
| grep | 19 | look、strings | 选取包含指定模式的行 |
| head | 16 | tail | 选取数据开头的若干行 |
| join | 19 | colrm、cut、paste | 依据公共字段合并数据列 |
| look | 19 | grep | 选取以指定模式开头的行 |
| nl | 18 | wc | 生成行号 |
| paste | 17 | colrm、cut、join | 合并数据列 |
| perl | — | awk | 编程语言:处理文本、文件、进程 |
| pr | 18 | fold、fmt | 把文本排版成页或栏 |
| rev | 16 | cat、tac | 颠倒每行数据中字符的顺序 |
| sdiff | 17 | cmp、comm、diff | 比较两个文件,显示差异 |
| sed | 19 | tr | 非交互式文本编辑 |
| sort | 19 | tsort、uniq | 对数据排序;检查数据是否已排序 |
| split | 16 | cat | 把大文件拆分成较小的文件 |
| strings | 19 | grep | 在二进制文件中查找字符串 |
| tac | 16 | cat、rev | 合并文件,同时倒序排列文本行 |
| tail | 16 | head | 选取数据末尾的若干行 |
| tr | 19 | sed | 更改或删除指定的字符 |
| tsort | 19 | sort | 从偏序关系生成全序排列 |
| unexpand | 18 | expand | 把空格转换成制表符 |
| uniq | 19 | sort | 选取重复/唯一的行 |
| wc | 18 | nl | 统计行数、单词数和字符数 |
| 过滤器 | 章 | 另见 | 用途 |
合并文件:cat
(Combining Files: cat)
相关过滤器:rev、split、tac
cat 程序把数据原封不动地复制到标准输出。数据 可以来自标准输入,也可以来自一个或多个文件。语法是:
cat [-bns] [file...]
其中 file 是文件名。
我们已经讲过使用 cat 程序的几种方式。但在所有 用途中,cat 最重要的用途无疑是合并多个文件。 下面是几个合并三个文件的典型例子。当然,你可以使用 任意多个文件。
cat name address phone
cat name address phone > info
cat name address phone | sort
这些用法值得记住,因为你会经常用到它们。作为参考, 它们汇总在图 16-2 中。
第一个例子中,cat 读取并合并三个文件的内容 (这里是 name、address 和 phone), 并把输出显示在你的屏幕上。通常,只有当这些文件短到 合并后的输出不会滚出屏幕时,你才会这样用。更常见的 做法是把输出通过管道交给 less(第 21 章), 一次一屏地显示。例如:
cat name address phone | less
第二个例子合并同样这三个文件,但把标准输出重定向到 另一个文件(这里是 info)。如果该文件不存在, Shell 会创建它;如果已存在,它会被替换,也就是说文件 原来的数据将永远丢失。(关于重定向与文件替换的讨论, 见第 15 章。)
第三个例子合并同样的文件,并把输出通过管道交给另一个 程序做进一步处理,这里是 sort(第 19 章)。
用 cat 合并文件时,有一个常见错误你必须避免: 不要把输出重定向到某个输入文件。例如,假设你想把 address 和 phone 的内容追加到文件 name 的末尾。你可能以为,只要把三个文件合并起来, 再把结果存进 name 就行:
cat name address phone > name
这样行不通,原因在于 Shell 处理重定向的方式。程序要把 标准输出重定向到一个文件之前,Shell 必须先确保这个文件 存在且为空。在本例中,如果 name 不存在,Shell 就创建它;如果 name 已存在,Shell 就会清空它。 于是在我们的例子中,等到 cat 准备从 name 读取时,这个文件已经是空的了。
当你输入上面那样的命令时,你会看到类似这样的消息:
cat: name: input file is output file
这看起来像是警告消息,但实际上为时已晚。即使按下 ^C(中止命令)也无济于事。等你看到这条消息时, name 的内容已经被删除了。
把 address 和 phone 的内容追加到文件 name 末尾的安全做法是使用:
cat address phone >> name
注意,我们没有把输出文件当作输入文件。相反,我们是把 其他所有文件的内容追加到输出文件的末尾。
作为 cat 讨论的结尾,下面列出它最有用的几个选项:
• -n(number,编号)选项会在每一行前面加上行号。
• -b(blank,空行)选项与 -n 搭配使用, 告诉 cat 不要给空行编号。
• -s(squeeze,压缩)选项会把连续多于一行的空行 压缩成单独一个空行。
名称的由来(What's in a Name?)
cat
cat 程序的主要用途,是把多个文件的内容合并成 单一的输出流。因此,你会很自然地以为 cat 是 "concatenate"(串联)的缩写。事实并非如此。
cat 这个名字源自一个古语词 "catenate",意思是 "连接成链"。所有受过古典教育的 Unix 用户都知道, catena 是拉丁语中"链条"的意思。
图 16-2:cat 程序的多种用途(Figure 16-2: The Many Uses of the cat Program)
cat 程序是最简单的过滤器。它从标准输入读取、 向标准输出写出,不修改数据。尽管如此简单,cat 却能完成数量惊人的任务,表中做了汇总。这样一个简单 过滤器之所以强大,源自 Unix 输入输出重定向与管道功能的 丰富性。
大多数时候,cat 用于合并文件:或是为了显示 (把输出通过管道交给 less),或是为了存入另一个 文件(把标准输出重定向到那个文件),或是为了通过管道 交给另一个程序做进一步处理。详见正文。
| 语法 | 用途 |
|---|---|
| cat > file | 从键盘读取,创建新文件或替换已有文件 |
| cat >> file | 从键盘读取,追加到已有文件 |
| cat < file | 显示已有文件 |
| cat file | 显示已有文件 |
| cat < file1 > file2 | 复制文件 |
| cat file1 file2 file3 | less | 合并多个文件,一次一屏地显示 |
| cat file1 file2 file3 > file4 | 合并多个文件,把输出保存到另一个文件 |
| cat file1 file2 file3 | program | 合并多个文件,把输出通过管道交给另一个程序 |
拆分文件:split
(Splitting Files: split)
相关过滤器:cat
我们刚讨论过如何用 cat 把两个或更多文件合并成 一个大文件。如果你想做相反的事:把一个大文件拆分成 若干小文件呢?为此你要用 split 程序。语法是:
split [-d] [-a num] [-l lines] [file [prefix]]
其中 num 指创建文件名时用作后缀的字符数或位数; lines 指每个新文件的最大行数;file 是输入 文件名;prefix 是创建文件名时使用的名称。
split 程序诞生于 20 世纪 70 年代初, 那时大型文本文件会带来不少麻烦。当时磁盘存储有限, 处理器也很慢。(*) 如今大容量硬盘无处不在,计算机也 极快。因此,我们很少会为存储和处理大型文本文件而烦恼。 不过仍然会有需要把大文件切成小块的时候,这时 split 能为你省下大量时间。例如,你可能想把一个 很大的文件用电子邮件发给某个收件大小受限的邮箱。
* 脚注
1976 年,我还是研究生一年级、刚开始用 Unix 时, 写过一个 C 程序,对一个文件中的数据做数学处理; 以今天的标准看,那个文件相当小。但在当时,它被算作 大文件,而计算机的内存远远装不下全部数据。结果,我的 程序必须非常复杂,因为它得能把数据分成小块来处理, 这些小块要在需要时换进换出内存。
默认情况下,split 创建的文件每个长 1,000 行。 例如,假设你有一个名为 data 的文件,共 57,984 行, 你想把它拆成较小的文件。你会使用命令:
split data
这会创建 58 个新文件:57 个每个含 1,000 行的文件, 以及最后 1 个包含剩下 984 行的文件。
如果你想改变文件的最大行数,用 -l(lines,行数) 选项。例如,要把文件 data(57,984 行)拆分成每个 含 5,000 行的文件,你会使用:
split -l 5000 data
这条命令创建 12 个新文件:11 个每个含 5,000 行的文件 (共 55,000 行),以及最后 1 个含 2,984 行的文件(余下的部分)。
— 提示 —
当你使用需要大数字的选项时,不要用逗号(在欧洲是句点) 把数字按三位一组分开。例如,你应当使用:
split -l 5000 data
而不应使用:
split -l 5,000 data
如果你用了逗号,就会造成语法错误,你会看到类似这样的 错误消息:
split: 5,000: invalid number of lines
到这里你大概在想:这些新文件都叫什么名字? split 既然要自动创建文件,就应该使用有意义的名字; 但它同时也必须小心,不能意外地替换掉你已有的文件。
默认情况下,split 使用的名字以字母 x 开头, 后跟一个 2 字符的后缀。后缀依次是 aa、ab、 ac、ad,如此继续。比如,在最后一个例子中 split 创建了 12 个新文件,它们的默认名字是:
xaa xab xac xad xae xaf
xag xah xai xaj xak xal
如果 split 需要超过 26 个文件,xaz 之后的名字 就是 xba、xbb、xbc,依此类推。由于字母表 有 26 个字母,这样最多可以有 676(26x26)个新文件名, 即从 xaa 到 xzz。
如果你不喜欢这些名字,有两种办法可以改变它们。第一, 如果使用 -d(digits,数字)选项,split 会用 从 00 开始的 2 位数字作为文件名结尾,而不是 2 个 字母的后缀。例如,考虑下面这条命令,它用的还是我们上面 那个文件 data(含 57,984 行):
split -d -l 5000 data
12 个新文件的名字是:
x00 x01 x02 x03 x04 x05 x06 x07 x08 x09 x10 x1l
如果你不想让文件名以 x 开头,可以自己指定一个 前缀,例如:
split -d -l 5000 data harley
新文件的名字是:
harley00 harley01 harley02 harley03
harley04 harley05 harley06 harley07
harley08 harley09 harley10 harley11
当 split 配合 -d 选项使用时,你可以用 00 到 99 的后缀创建最多 100 个文件(10x10)。 不用 -d 时,你可以用 aa 到 zz 的后缀创建 最多 676 个文件(26x26)。如果你需要更多文件,可以用 -a 选项,后面跟上你希望后缀包含的位数或字符数。 例如:
split -d -a 3 data
新文件名将使用 3 位数字后缀:
x000 x001 x002 x003...
类似地,你也可以只用 -a 而不加 -d 选项:
split -a 3 data
这种情况下,新文件名使用 3 个字母的后缀:
xaaa xaab xaac xaad...
这样,你就可以用 split 拆分极大的输入文件, 而不必担心文件名不够用。
默认情况下,split 创建 1,000 行的文件。但如我所说, 你可以创建任意大小的文件,哪怕很小。下面是一个典型的 日常例子,我相信你一定能感同身受。
你为一位很有实力的美国参议员工作,他正在竞选美国总统。 距离选举只有两周了,选情不容乐观。参议员十分焦急, 而因为你会 Unix,你被提升为新的幕僚长。
上岗第一天,你拿到一个很大的文本文件,名为 supporters,其中每一行包含一位潜在选民的名字和 电话号码。你的任务是组织全国各地的志愿者,给名单上的 所有人打电话,劝说他们投票支持你的候选人。你判断每位 志愿者只有时间打 40 个电话。你登录 Unix 系统,输入命令:
split -d -l 40 supporters voter
现在你得到了一组文件,名为 voter00、 voter01、voter02,依此类推。这些文件中的 每一个(可能除最后一个之外)都恰好包含 40 个名字。 你吩咐手下的工作人员给每位志愿者发一封电子邮件,附上一个 文件,并说明给他或她名单上的全部 40 个人打电话。
由于你的辛勤工作和出众的 Unix 技艺,你的候选人当选了。 一周之内,你就被任命担任一个权力极大、影响极广的职务。
合并文件并倒序排列行:tac
(Combining Files While Reversing Lines: tac)
相关过滤器:cat、rev
如我们所讨论的,cat 是所有过滤器中最基本的一个, 同时也是最有用的过滤器之一。tac 程序与 cat 相似,但有一处重大差别:tac 在把文本行 写到标准输出之前,会颠倒它们的顺序。(名字 tac 就是把 cat 倒过来拼写。)
tac 的语法是:
tac [file...]
和 cat 一样,tac 从标准输入读取、向标准输出 写出,而且 tac 也能合并输入文件。比如,你有一个 名为 log 的文件。你想把 log 中所有行的顺序 颠倒过来,并把结果写入一个名为 reverse-log 的新 文件。要使用的命令是:
tac log > reverse-log
例如,假设 log 的内容是:
Oct 01: event 1 took place
Oct 02: event 2 took place
Oct 03: event 3 took place
Oct 04: event 4 took place
执行上面的 tac 命令之后,reverse-log 的内容 就是:
Oct 04: event 4 took place
Oct 03: event 3 took place
Oct 02: event 2 took place
Oct 01: event 1 took place
到这里,你也许会想:tac 该不会只是个新奇玩意儿 吧?也许它被写出来,只是因为有人觉得这个名字(把 cat 倒过来)很有趣。可是,你真的会用上这个程序吗?
答案是:你并不经常需要 tac。然而,当你需要它的 时候,它价值千金。例如,假设你有个程序会把记录写进 日志文件(这是常见情形)。最早的记录在文件开头,最新的 记录在文件末尾。这个名为 log 的文件现在有 5,000 行,而你想按从新到旧的顺序显示这些记录。
没有 tac 的话,想把一个长文件的行倒序显示并没有 简单的办法。有了 tac 就容易了:用 tac 把行 倒过来,再把输出通过管道交给 less(第 21 章):
tac log | less
如果你还需要合并文件,同样可以做到,例如:
tac log1 log2 log3 | less
这条命令把三个文件的行倒序排列,合并起来,然后把输出 通过管道交给 less。
颠倒字符顺序:rev
(Reversing the Order of Characters: rev)
相关过滤器:cat、tac
tac 程序颠倒文件内各行的顺序,但如果你想颠倒 每一行内部字符的顺序呢?这种情况下,你用 rev。 语法是 rev:
rev [file...]
其中 file 是文件名。
这里举个例子。你有一个名为 data 的文件,内容是:
12345
abcde
AxAxA
你输入:
rev data
输出是:
54321
edcba
AxAxA
假设你想颠倒每一行内字符的顺序,并且颠倒文件中 各行的顺序。只要把 rev 的输出通过管道交给 tac 即可,例如:
rev data | tac
输出是:
AxAxA
edcba
54321
你认为,如果先用 tac 会怎样?
tac data | rev
为了把本节讲完,再看一个例子。你有一个名为 pattern 的文件,包含下面 4 行:
X
XX
XXX
XXXX
看看下面四条命令的输出($ 是 Shell 提示符):
$cat pattern
X
XX
XXX
XXXX
$tac pattern
XXXX
XXX
XX
X
$rev pattern
X
XX
XXX
XXXX
$rev pattern | tac
XXXX
XXX
XX
X
这些你都能想通吗?
选取数据开头或结尾的若干行:
head、tail
(Selecting Lines From the Beginning
or End of Data: head, tail)
当数据多到你无法轻松理解时,有两个程序可以让你快速 选取其中一部分数据:head 命令选取数据开头的行; tail 命令选取数据末尾的行。
大多数时候,你会用 head 和 tail 来显示文件的 开头或结尾。因此,我会把对这两个命令的主要讨论推迟到 第 21 章,那时我们讲文件显示命令。本节我要向你展示的, 是如何把 head 和 tail 当作管道中的过滤器使用。
把 head 和 tail 当作过滤器使用时,语法很简单:
head [-n lines]
tail [-n lines]
其中 lines 是你想选取的行数。(在第 21 章, 我们会用到更复杂的语法。)
默认情况下,head 和 tail 都选取 10 行数据。 例如,假设你有一个叫 calculate 的程序,会生成 大量数据行。要显示前 10 行,你会使用:
calculate | head
要显示最后 10 行,你会使用:
calculate | tail
如果你想选取不同的行数,就用一个连字符(-)再加上 那个数字。例如,要选取 15 行,你会使用:
calculate | head -n 15
calculate | tail -n 15
你常常会在一条复杂管道的末尾使用 head 和 tail,从前面的命令生成的数据中选取一部分。例如, 你有四个文件:data1、data2、data3 和 data4。你想合并这些文件的内容,把全部内容排序, 然后显示最前和最后的 20 行。
合并文件用 cat 程序(本章前面讲过)。执行排序用 sort 程序(第 19 章):
cat data1 data2 data3 data4 | sort | head -n 20
cat data1 data2 data3 data4 | sort | tail -n 20
有时你会想把 head 或 tail 的输出再交给另一个 过滤器。例如,下面这条管道从 sort 的输出中选取 开头 300 行,然后我们把它送给 less(第 21 章), 一次一屏地显示:
cat data1 data2 data3 data4 | sort | head -n 300 | less
类似地,把 head 或 tail 的输出保存到一个文件 往往也很顺手。下面的例子选取输出最后的 10 行,保存到 名为 most-recent 的文件:
cat data1 data2 data3 data4 | sort | tail > most-recent
— 提示 —
最初,head 和 tail 并不要求使用 -n 选项;你可以只输入一个连字符再跟一个数字。例如,下面 这些命令都会显示 15 行输出:
calculate | head -n 15
calculate | tail -n 15
calculate | head -15
calculate | tail -15
按官方说法,现代版本的 head 和 tail 应当要求使用 -n 选项,所以我才把它写出来。 不过,大多数 Unix 和 Linux 版本两种语法都接受,所以 — 只要你妈妈没在旁边看着 — 你通常可以省略 -n。
删除数据列:colrm
(Deleting Columns of Data: colrm)
相关过滤器:cut、paste
colrm("column remove",删除列)程序从标准输入读取, 删除指定的数据列,然后把剩下的数据写到标准输出。语法是:
colrm [startcol [endcol]]
其中 startcol 和 endcol 指定要删除的列的 起止范围。列号从第 1 列开始计。
这里举个例子:你是加州某学院的终身教授,需要一份你 PE 201 课程("中级冲浪")所有学生的成绩清单。这份清单 不应显示学生的名字。
你有一个主数据文件,名为 students,其中每个学生 占一行信息。每一行包含学号、姓名、期末考试成绩和课程 成绩:
012-34-5678 Ambercrombie, Al 95% A
123-45-6789 Barton, Barbara 65% C
234-56-7890 Canby, Charles 77% B
345-67-8901 Danfield, Deann 82% B
要做出这份成绩清单,你需要删掉名字,它们在第 14 列到 第 30 列(含两端)。使用命令:
colrm 14 30 < students
输出是:
012-34-5678 95% A
123-45-6789 65% C
234-56-7890 77% B
345-67-8901 82% B
作为对管道和重定向的快速复习,我再举两个例子。首先, 如果这份清单碰巧很长,你可以把它通过管道交给 less,一次一屏地显示数据:
colrm 14 30 < students | less
其次,如果你想保存输出,可以把输出重定向到一个文件:
colrm 14 30 < students > grades
如果你只指定起始列,colrm 会删除从该列到行末的 所有列。例如:
colrm 14 < students
显示:
012-34-5678
123-45-6789
234-56-7890
345-67-8901
如果你既不指定起始列也不指定结束列,colrm 什么 也不会删除。
练习
(Exercises)
复习问题 #1:
什么是过滤器?过滤器为什么如此重要?
复习问题 #2:
你需要用过滤器和一条管道来解决一个难题。 你应当遵循哪四个步骤?
你最需要的三项技能是什么?
复习问题 #3:
为什么说 cat 是最简单的过滤器?
尽管很简单,cat 却可以用于多种目的。说出其中四个。
复习问题 #4:
tac 和 rev 有什么区别?
应用你的知识 #1:
一位科学家做了一项实验,产生的数据累积存放在一连串 文件中:data1、data2、data3、 data4 和 data5。他想知道自己总共有多少行数据。
命令 wc -l 从标准输入读取并统计行数。你会如何用 这条命令统计这五个文件中的总行数?
应用你的知识 #2:
你有一个名为 important 的文本文件。要用以下四种 不同方式显示这个文件的内容,你分别使用什么命令?
(a) 原样显示
(b) 颠倒各行顺序
(c) 颠倒每行内字符的顺序
(d) 同时颠倒行序和字符顺序
对于 (b)、(c) 和 (d),哪条命令能做相反的变换? 你会怎么验证?
应用你的知识 #3:
在第 6 章中,我们讨论过 Linux 的 dmesg 程序, 它显示系统启动时生成的消息。通常这类消息非常多。 你要用什么命令来显示最后 25 条启动消息?
进一步思考 #1:
图 16-1 列出了最重要的 Unix 过滤器。如果不算 awk 和 perl(它们是编程语言), 表中共有 19 个不同的过滤器。对大多数问题,你只需要 一个过滤器;很少会需要四个以上。你认为这是为什么?
你能想到哪些工具是你认为清单里缺失的吗?
进一步思考 #2:
split 程序诞生于 20 世纪 70 年代初,那时大型文本 文件会带来麻烦,因为磁盘空间相对而言是缓慢的存储, 而且极其昂贵。如今磁盘又快又便宜。我们还需要 split 这样的程序吗?为什么?
© 本书全部内容,2026 年版权所有,Harley Hahn
完整的商标与版权信息