|
Harley Hahn 的
|
|
各章...
命令
教师 |
第 18 章... 过滤器:计数与格式化 (Filters: Counting and Formatting)
这是四章(第 16-19 章)中讨论过滤器的第三章。所谓过滤器, 就是一次一行地读写文本数据的程序: 它们从标准输入读取,写到标准输出。 在这一章里,我们要谈的是如何处理文本。 具体来说,我会向你展示如何处理行号; 如何统计行、单词和字符; 以及如何用各种实用的方式格式化文本。 在这个过程中,我们会稍稍岔开话题, 讲几个非常有意思的内容,包括 Unix 如何处理制表符和空格;以及为什么 你在文本文件里常常看到 80 字符一行—— 这个故事比你想象的要有趣得多。
相关过滤器:wc nl 过滤器提供了一项简单却实用的 服务:它把行号插入文本。语法是: nl [-v start] [-i increment] [-b a] [-n ln|rn|rz] [file...] 其中 start 是起始编号, increment 是增量,file 是 一个文件的文件名。 nl 程序在两种场合会很顺手。 第一,你想把行号永久地插入某些数据, 然后把结果保存起来。第二,你想把 临时行号插入某个命令的输出,让输出更容易 理解。我们先看一个简单但实用的 例子。 你要去相亲,而且确定想给对方 留下好印象。为了这次约会做准备, 你创建了一个名为 books 的文件, 里面列出你最喜欢的书:
Crime and Punishment
要给这个列表编号,你可以使用命令: nl books 输出是:
1 Crime and Punishment
看起来不错,于是你把标准输出重定向到 一个名为 best-books 的文件, 把这个带编号的列表保存下来: nl books > best-books 现在你有了一份带行号的最爱书单, 可以用它来让相亲对象对你刮目相看。 nl 是个古老的程序,可以追溯到 Unix 的早期。传统上,nl 用于在打印 之前把行号插入文本。举例来说, 假设你有两个科学数据文件: measurements1 和 measurements2。 你想把所有数据打印出来,并且为了方便 解读,想让打印出的每一行都带上 编号。然而你又不想改动 原始数据。 策略是用 nl 给各行编号,然后把输出 送到 lpr 程序,由它把数据交给你的默认 打印机。(Unix 中打印文件的两个主要 程序是 lp 和 lpr。) nl measurements1 measurements2 | lpr 这样一来,你创建的是临时编号, 用完即弃。 在 Unix 早期,终端把输出打印在纸上, 速度很慢,因此人们常常把数据送到真正的 打印机上,那比终端快得多。如今, 把数据显示在屏幕上通常更合理。 这种情况下,你只需要把 nl 的输出 通过管道交给 less(第 21 章), 它会一屏一屏地显示输出: nl measurements1 measurements2 | less 同样,原始数据没有被改动。使用 nl 时,除非你把输出保存成文件, 行号永远是临时的。 默认情况下,nl 生成 1、2、3 这样往下排的编号,这就够好了。不过 若有需要,你可以用几个选项来控制 编号。用 -v 选项可以改变起始编号, 用 -i 选项可以改变增量。为了让你 看看它是怎么工作的,这里有一些例子, 都用一个名为 data 的文件, 里面有几行文本。 第一个例子从 100 开始编号: nl -v 100 data 输出是:
100 First line of text.
下一个例子从 1(默认值)开始编号, 增量为 5: nl -i 5 data 输出是:
1 First line of text.
第三个例子同时使用两个选项, 从 100 开始编号,增量为 5: nl -i 5 -v 100 data 输出是:
100 First line of text.
除了 -v 和 -i,nl 程序 还有各种格式化选项。不过你可能需要 用到的只有两个。第一,默认情况下, 如果你的数据里有空行,nl 不会给它们 编号。要强制 nl 给所有行编号,请使用 -b(正文编号)选项,后面跟字母 a(all,所有行): nl -b a file -b 选项还有其他变体,但很少用到。 第二,你可以用 -n(数字格式)选项 控制数字的格式,后面跟一个代码:
ln = 左对齐,无前导零
这里是一个例子: nl -v 100 -i 5 -b a -n rz file 这条命令生成的编号从 100 开始,增量为 5。所有行都编号,空行也不例外,数字 右对齐并带前导零。
相关过滤器:nl wc(word count,单词计数)程序统计 行、单词和字符的数量。数据可以来自 另一个程序,也可以来自一个或多个文件。 语法很简单: wc [-clLw] [file...] 其中 file 是一个文件的文件名。 wc 程序非常有用,事实上比 你起初意识到的更有用。这是因为你可以 在管道中使用 wc,分析任何 程序产生的文本输出。举例来说,假设你想 知道某个目录里有多少个文件。 你可以逐个手工清点,也可以生成一个 列表,再通过管道交给 wc 替你把行数 出来。(我马上就会给你看一个例子。) 先说基本用法。默认情况下,wc 的输出 由三个数字组成:数据中的行数、单词数 和字符数。举例来说,我马上会让你看到 一个文件,wc 会发现它恰好包含 2 行、 13 个单词和 71 个字符。 当输入来自文件时,wc 会在这三个数字 之后写出文件名。如果你指定了不止一个 文件,wc 会为每个文件显示一行输出, 再多出一行,给出所有文件合在一起的 总数——行、单词和字符的总数。 例子来了。情人节到了,你正在为 心上人写一首浪漫的情诗。到目前为止 你只写出这些:
There was a young man from Nantucket,
要统计这首诗的行数、单词数和字符数, 使用: wc poem 输出是: 2 13 71 poem 换句话说,这个文件有 2 行、13 个单词和 71 个字符。如果你忘了哪个数字对应什么, 只要记住:Lines(行)、Words(单词)、 Characters(字符)。(如果你是男性,可以 记住首字母缩写 LWC——"Look at Women Carefully",仔细看女人。) 技术细节如下: • "字符"指一个字母、数字、标点符号、空格、制表符或换行符。 • "单词"指一段连续不间断的字符,以空格、制表符或换行符分隔。 • "行"指以换行符结尾的一段字符。(换行符在第 7 章讨论。) 如我所说,如果你一次指定了不止一个 文件,wc 还会给出汇总统计。 例如: wc poem message story 下面是某种典型输出(*):
2 13 71 poem
* 脚注 这些文件里是真实数据。poem 就是我们 上面用到的那首示例诗;message 是我编辑 发来的一封电子邮件,日期是 2006 年 2 月 16 日,问这本书什么时候能写完; story 是我写的一个短篇故事,名叫 "Late One Night"(《深夜》),你可以在我的 网站 www.harley.com 上找到它。 按惯例,输出总是按这个顺序显示: 行数、单词数、字符数。如果你不想要 全部三个数字,可以使用选项:-l 统计 行数,-w 统计单词数,-c 统计 字符数。使用选项时,wc 只显示你 要求的那些数字。例如,只想看 story 文件的行数,使用: wc -l story 输出是: 43 story 要看 message 文件里有多少单词和 字符,使用: wc -wc message 输出是: 61 447 message -c(字符)、-l(行)和 -w(单词) 这几个选项几十年来一直是 wc 命令的 一部分,可用于任何种类的 Unix 或 Linux。在 Linux 上还有一个额外的 选项 -L。它显示输入中最长一行的 长度。举例来说,假设你在筹划一场 盛大的派对,需要给门卫一份名单,列出 所有不许入场的人。你创建了文件 do-not-admit,里面有以下四行:
Britney
要显示这个文件中最长一行的长度, 你会使用: wc -L do-not-admit 在这种情况下,第一行和第四行有 7 个 字符,所以输出是: 7 do-not-admit 当你需要判断某个文件是否要做某种 格式化时,-L 选项会很顺手。例如, 如果文件里有超过 70 个字符的行, 你可以先用 fmt 格式化文本,再交给 pr 做打印准备(本章后面会讲)。 随着经验的增长,你会发现 wc 有两种 主要用法。第一种,有时你需要快速 衡量文件的大小。举例来说,你通过电子 邮件把一个文本文件发给别人。这个文件 很重要,你想再次确认它完好送达。 对原文件运行 wc 命令,然后请收件人 对他那份文件运行 wc。如果两组结果 一致,你就可以确信文件完好无损地 到达了。类似地,假设你在写一篇至少 2000 个单词的文章,你可以不时用 wc -w 看看自己离目标还有多远。 wc 的第二种用法不同,但同样重要: 你可以把命令的输出通过管道交给 wc,看看生成了多少行文本。因为很多 程序每行输出一条信息,所以你数出 行数,就知道产生了多少信息。 这里有两个例子。 第一个,ls 程序(第 24 章)列出目录中 的文件名。例如下面这条命令显示 /etc 目录下所有文件的名字。 (我们在第 24 章讨论目录。) ls /etc ls 程序有很多选项,但没有统计 文件数量的选项。要做到这一点,你把 ls 的输出通过管道交给 wc。因此, 要统计 /etc 目录中的文件数,你使用: ls /etc | wc -l (在你自己的系统上试试看。) 第二个例子在这里。第 8 章里,我向你 展示过如何用 who 查看哪些用户标识 登录到了你的系统。要显示登录的用户 标识(*)数目,你只要数一数 who 命令输出的行数: who | wc -l * 脚注 用户标识(读音为 "user-eye-dee")并不是 人。它是用来登录 Unix 系统的一个名字。 正如我们在第 4 章讨论过的,Unix 只知道 用户标识,不知道用户。 如果你想玩点花样,可以把最后这条管道 与 echo 程序(第 12 章)和命令替换 (第 13 章)结合起来,显示一条消息,说明 当前登录的用户标识有多少: echo "There are `who | wc -l` userids logged in right now." 例如,如果有 5 个人登录,你会看到: There are 5 userids logged in right now. 如果你使用的是共享的多用户系统, 把这条命令放进你的登录文件(见第 14 章) 会很有意思。
看看你的键盘,你会看到一个 <Tab> 键。这个键是打字机时代使用制表符留下的 遗物。虽然我们不再使用制表符了,但我们 仍然用着 <Tab> 键,Unix 也仍然 使用制表位设置。要明白原因,我们花点 时间坐上时光机,回到打字机还是 办公设备群落中主宰物种的年代。 "tab" 这个词是 "tabulate"(制表)的 缩写,意思是把信息组织成表格。老式 打字机上的 <Tab> 键,设计用途是 帮助你把信息排成各列,并在段首缩进 文本。下面举个例子说明它当时是怎么 工作的。 你正在使用一台老式打字机,想打出一个 三列的表格。三列要分别对齐到第 1、15 和 25 个位置。准备工作是在第 15 和第 25 个位置上架好两个叫做 TAB STOPS(制表位) 的小机械标记。 这样一来,按下 <Tab> 键就会让滑架 横向移动到下一个制表位。例如,如果你在 第 8 个位置,按下 <Tab>,滑架就会移到 第 15 个位置。如果你在第 19 个位置, 按下 <Tab>,滑架就会移到第 25 个 位置。因此,这样设置制表位,你就有了 一个简便的办法,能直接跳到第 15 和 第 25 个位置,不必反复按 <Space> 空格键(也不必在按过头时往回退格)。 现在你可以开始打表格了。首先放进一张 纸,把滑架移到行首。打出第一列的信息, 然后按下 <Tab> 键。这会让滑架移到 第 15 个位置。打出第二列的信息,再按一次 <Tab>。滑架这时移到第 25 个位置。 然后打出第三列的信息。到这里,表格 第一行就打完了。你把位于最左边的回车杆 按下去,它把滑架移到下一行的行首, 于是你可以接着打下一行。 尽管最初的 Unix 终端(见第 7 章)不是 打字机,但它们确实把内容打印在纸上, 而且遇到制表符时能够横向跳跃。 出于这个原因,Unix 被设计成:每当终端 遇到一个制表符,就像打字机那样把光标 移到当前行的下一个制表位——直到 今天仍然如此。Unix 终端"显示"制表符的 方式,就是把光标向前移到下一个制表位。 默认情况下,Unix 假定从第 1 个位置起, 每隔 8 个字符有一个制表位。因此 Unix 的 默认制表位是 1、9、17、25、33,依此类推。 当你键入文本并按下 <Tab> 键时, Unix 会插入一个不可见的制表符。之后 你查看文本时,终端会造出足够的横向 空间,跳到下一个制表位,以此"显示"这个 制表符,就像老式打字机上的 <Tab> 键一样。 看这个例子。你有一个只有一行的文件, 内容是一个字母 "A"、一个制表符、 五个字母 "BBBBB"、又一个制表符, 以及字母 "CCC": A<Tab>BBBBB<Tab>CCC 如果你用 cat 命令显示这个文件, 你会看到: A BBBB CCC A 在第 1 个位置。BBBB 从第 9 个 位置开始,CCC 从第 17 个位置开始。 当然,你看不见制表符:它们看起来就是 空白。所以在你的眼里,字母之间的间隔 跟空格字符没什么两样。例如在上面这个 例子里,你看 cat 命令的输出时, 分不出 A 和 BBBB 之间的空白是 (在这个例子中)1 个制表符还是 7 个空格。 于是问题来了:当你想缩进文本或把数据 对齐成列时,用制表符还是用空格更好? 程序员们为这个问题争论了很久,因为他们 要用空白来缩进控制流结构(if-then-else、 while 循环等等)。 一些程序员更喜欢用制表符来缩进,因为 它更简单。例如,你每按一次 <Tab>, 就插入单个制表符,自动把文本缩进到下 一个制表位。如果用空格,你就得反复按 <Space> 键,手工把文本对齐。 此外,制表符也比空格更灵活。例如,如果 你想改变屏幕上看到的缩进量,只要在文本 编辑器程序里改变制表位设置即可。如果用 空格,你就得逐行去增加或删除真正的 空格字符。 另一些程序员则主张用空格缩进。他们说 制表符用起来笨拙,因为它产生的空白量 会变。他们指出,单个制表符可能代表 1 到 8 个位置,取决于它在行内的位置。而用 空格时,一分投入就有一分收获:键入 4 个 空格,就得到 4 个空格。 而且,虽然你确实可以在大多数文本编辑器 里调整制表位设置,但多数时候你还是会被 默认值困住:也就是 1、9、17、25 等位置。 这么大的间隔实在太过,它造成很大的 缩进,让文本难以阅读。使用真正的空格, 你可以缩进 2 个、3 个或 4 个位置—— 想要多少都可以——而且无论你用什么文本 编辑器或其他程序,它都会完全按你想要的 方式工作。 当然,需要制造横向空白的地方远不止 计算机程序。只要你处理的文本需要缩进 或排成各列,你就必须在制表符和空格 之间做出选择。 我没法告诉你该用哪个,因为每个人都有 自己的偏好,而且随着时间推移,你会 弄清楚哪个更适合你。(就个人而言, 我更喜欢空格而不是制表符。)(*) 我能告诉你的是——无论你最终做出哪种 选择——都有两个 Unix 程序能让你的日子 更轻松(expand 和 unexpand), 我们马上就讲到。不过首先,我们需要 讨论一个更基本的问题。 * 脚注 我写程序时缩进 4 个位置。我写 HTML (网页)时缩进 2 个位置。
当你处理一个同时含有制表符和空格的 文件时,问题就来了。既然制表符和空格 都看不见,你怎么知道它们在哪儿? 当你使用 expand 和 unexpand 这类程序时(下两节讨论),这一点可能很 重要。expand 程序把制表符变成空格; unexpand 把空格变成制表符。如果你 看不见制表符和空格,你怎么知道这些命令 做出了你想要的效果? 最简单的解决办法,是在一个允许你打开 "查看不可见字符"选项的文本编辑器或 文字处理器里查看文件。有两种选择。 用 vi 编辑器(第 22 章)时,要使用的 命令是: :set list 空格仍然看不见,但制表符会显示为 ^I,即 ASCII 码中代表制表符的控制 字符。要关闭这个选项,使用: :set nolist 如果你会用 vi,这就是解决这个问题 的好办法:又快又省力。事实上,我就是 这样查看文件中的空白的。 如果你不会 vi,可以使用 Nano 或 Pico 编辑器,我在第 14 章提到过它们。(它们 基本是同一个编辑器;Nano 是 Pico 的 GNU 版本。)在 Nano/Pico 里,你按 <Esc>P 就能查看空格和制表符 (也就是先按 <Esc> 键,再按 <P> 键)。这会打开 "Whitespace display mode" (空白显示模式)。要关掉它,再按一次 <Esc>P 即可。 不过,在你使用 <Esc>P 之前, 必须先在你的 Nano/Pico 初始化文件—— 分别是 .nanorc 或 .picorc (见第 14 章)——里加上下面这一行: set whitespace "xy" 其中 x 是你想用来表示制表符的字符, y 是你想用来表示空格的字符。 例如,如果你想让制表符显示为 + (加号)字符,让空格显示为 |(竖线) 字符,就在你的 Nano/Pico 初始化文件里 放入下面这一行: set whitespace "+|" 用 vi 或 Nano/Pico 查看空格和制表符 效果很好。遗憾的是,vi 很复杂(你在 第 22 章就会看到),要学会熟练使用它得花 很长时间。Nano/Pico 简单得多,但和 vi 一样,学起来也要花时间。 那么我们在第 14 章讨论过的、非常简单的 图形界面编辑器 Kedit 和 Gedit 呢? 如我所说,这些编辑器非常好用。但它们 不够强大。尤其是,它们不允许你查看 不可见字符,所以暂时就把它们放一边吧。 如果你的系统里有 Open Office(一套开源 办公应用),那还有另一个办法。Open Office 的文字处理器让查看文件中的制表符和空格 变得很容易。只要打开 View(视图)菜单, 选择 "Nonprinting Characters"(不可打印 字符)。关掉这个功能的方法也一样。 简单省事。 除了在文本编辑器或文字处理器里查看 文件,还有一个间接检验 expand 或 unexpand 效果的办法。你可以使用 wc -c 命令(本章前面讲过)显示文件中 的字符数。 由于每个制表符都是单个字符,当你用 expand 把文件中的制表符改成空格时, 文件的字符数会增加。类似地,当你用 unexpand 把空格改成制表符时,文件 的字符数会减少。虽然 wc -c 不会显示 不可见字符,但它能让你看出 expand 或 unexpand 究竟起没起作用。
相关过滤器:unexpand 正如本章前面讨论的,当你需要缩进文本 或把数据对齐成列时,制表符和空格都可以 用,选择权在你。但无论你的偏好如何, 总有些时候你会发现自己在处理带有 制表符的数据,而你需要把它们改成空格。 同样,也总有些时候你手上有带空格的 数据,需要改成制表符。这种情况下,你 可以用 expand 程序把制表符改成空格, 用 unexpand 程序把空格改成制表符。 先从 expand 开始。 语法是: expand [-i] [-t size | -t list] [file...] 其中 size 是固定宽度制表符的宽度, list 是制表位的列表,file 是 一个文件的文件名。 expand 程序把输入文件中所有的制表符 都改成空格,同时保持与原文相同的对齐。 默认情况下,expand 采用 Unix 惯例, 即每 8 个位置设一个制表位:1、9、17、25、 33,依此类推。因此,输入中的每个制表符 在输出中都会被替换成 1 到 8 个空格。 (好好想想,直到想通为止。) 举个例子,看下面这个名为 animals 的 文件,它的内容是按列组织的数据。 显示 animals 时,你会看到:
kitten cat
如你所见,文件里有四行数据。你看不见 的是:每一行都由两个以制表符分隔的 单词组成:
kitten<Tab>cat
(我用 <Tab> 这个记号来表示单个 制表符。) 下面这条命令把每个制表符展开为适当 数量的空格,并把输出保存到名为 animals-expanded 的文件中: expand animals > animals-expanded 新文件现在包含以下内容:
kitten<Space><Space>cat
(我用 <Space> 这个记号来表示单个 空格字符。) 如果你用 cat 或 less 显示新文件, 它看起来和原文件一模一样。但如果你用 文本编辑器或文字处理器查看不可见字符 (正如我在上一节所描述的),你会看到 所有制表符都变成了空格。更具体地说, 在每一行里,expand 都去掉了制表符, 并插入足够的空格,使后面的单词从下一个 制表位开始,在这个例子里就是第 9 个 位置。 如我所说,expand 采用 Unix 默认设置, 假定每两个制表位之间相隔 8 个位置。 你可以用 -t(制表位)选项改变这一点。 它有两种形式。第一,如果所有制表位 间距相同,就用 -t 后跟那个数字。 例如,假设你有一个名为 data 的大 文件,里面有一些制表符。你想把制表符 展开为空格,并把输出保存到 data-new 文件里。不过你希望制表位设在每 4 个字符 处,而不是每 8 个字符处;也就是说,你 想要:1、5、9、13,依此类推。使用命令: expand -t 4 data > data-new 用这种记法,我们可以说 -t 8 就相当于 Unix 默认值,即每 8 个位置一个制表位; -t 4 则创建每 4 个位置一个制表位。 -t 还有第二种形式。如果你希望制表位 落在指定位置,可以指定一个由逗号分隔的、 包含多个数字的列表。在这个列表里, 编号从 0 开始。也就是说,0 指行内的 第一个位置;1 指行内的第二个位置; 依此类推。例如,要把制表位设在第 8、16、 22 和 57 个位置,你会使用: expand -t 7,15,21,56 data > data-new 最后,当你想展开制表符、但只处理行首的 制表符时,有一个选项可用。这种情况下 使用 -i(initial,行首)选项,例如: expand -i -t 4 data > data-new 这条命令展开制表符,但只处理行首的。 其余制表符一概不动。在这种情况下, 由于 -t 选项,制表位被认为是 1、5、9 等位置。 提示 expand 程序适合用来预处理带制表符的 文本文件,然后再把这些文件交给那些指望 各列完全对齐的程序。 例如,下面这条管道替换名为 statistics 的文件中的所有制表符, 该文件的制表位在每 4 个位置处。替换 制表符之后,提取每行的前 15 个字符, 并对结果排序: expand -t 4 statistics | cut -c 1-15 | sort
相关过滤器:expand 要把空格改成制表符,你使用 unexpand 程序。语法是: unexpand [-a] [-t size | -t list] [file...] 其中 size 是固定宽度制表符的宽度, list 是制表位的列表,file 是 一个文件的文件名。 unexpand 程序的工作方式正如你所预料, 就像反着来的 expand,用制表符替换 空格,同时保持数据原有的对齐。 和 expand 一样,默认的制表设置是每 8 个位置:1、9、17,依此类推。要改变这一点, 你可以使用与 expand 相同的两种 -t 选项形式:固定间隔(例如 -t 4, 每 4 个位置),或制表位列表(例如 -t 7,15,21,56)。如果使用列表,编号从 0 开始。也就是说,0 指行内的第一个 位置;1 指行内的第二个位置;依此类推。 expand 和 unexpand 的一个重要 区别是:默认情况下,unexpand 只替换 行首的空格。这是因为多数时候你只会把 unexpand 用在缩进的行上。你可能并不想 替换行中间的空格。不过,如果你确实想 推翻这个默认行为,可以使用 -a(all, 全部)选项。它会告诉 unexpand 替换所有 空格,包括不在行首的那些。 举个例子,假设你是西海岸一所名牌大学的 学生,专业是美国当代文化。你刚刚听完 一场关于米老鼠(Mickey Mouse)的讲座,期间 认真做了笔记。回到家,你用文本编辑器把 笔记打进一个名为 rough-notes 的文件。 恰好,你的编辑器在缩进行时,每缩进一级 会插入 4 个空格: |
|
Mickey Mouse (1928-)
|
|
你想把所有行首的空格改成制表符,并把 数据保存到一个名为 mickey 的文件里。 要使用的命令是: unexpand -t 4 rough-notes > mickey 一旦你运行这条命令,mickey 文件里就 包含了: |
|
Mickey Mouse (1928-)
|
|
"Man cannot survive except through his mind. But the mind is an attribute of the individual.
|
|
你想把这个段落格式化成 40 字符的行。 为此你使用命令: fold -w 40 speech 然而,fold 在恰好 40 个字符处断行, 这就意味着有些行是在单词中间断开的:
"Man cannot survive except through his m
取而代之,你使用 -s 选项告诉 fold 不要拆断单词: fold -s -w 40 speech 输出是:
"Man cannot survive except through his
虽然右边距有点参差,但单词保持完整。 如果你想保存格式化后的文本,只要把输出 重定向即可: fold -s -w 40 speech > speech-formatted 提示 对于某些程序,你会发现自己每次使用它时 都用同样的选项。为了让工作更顺畅,你可以 定义一个包含这些选项的别名(见第 13 章), 这样就不必每次都键入它们。 举个例子,假设你总是用 fold 搭配 -s -w 40。你可以把下面两个别名定义中 的一个放进你的环境文件(见第 14 章)。 第一个定义适用于 Bourne Shell 家族; 第二个适用于 C-Shell 家族:
alias fold="fold -s -w 40"
现在,每当你使用 fold,自动就会带上 -s -w 40 这些选项。 如果你偶尔想在不用 -s -w 40 的情况下 运行 fold,你也可以办到(正如我们在 第 13 章讨论过的):在命令名前面键入一个 \(反斜杠)字符,暂时让别名失效。 例如,如果你想用 -w 60 而不是 -s w 40 来使用 fold,你可以使用 命令: \fold -w 60 long-text > short-text 要在不带任何选项的情况下运行 fold, 使用: \fold long-text > short-text
多年来,程序员每行文本用 80 个字符, 终端每行输出也显示 80 个字符。随着图形 用户界面的出现——它允许你动态调整窗口 大小——魔数 80 作为精确行长出现的次数 少了。尽管如此,仍有很多 Unix 程序默认 使用每行 80 个字符,例如: • fold 程序(我们在上一节讨论过)默认在第 80 个位置断行。 • 如果你仔细查看在线 Unix 手册(第 9 章)的页面,你会发现它们是按 80 字符一行排版的。 • 当你使用终端仿真程序(第 3 章)时,默认行宽通常是 80 个字符。 为什么会这样?每行 80 个字符有什么 特别的?故事是这样的。 1879 年,美国发明家赫尔曼·霍勒瑞斯 (Herman Hollerith,1860-1929)正在研制 一套系统,用来处理即将进行的 1880 年 美国人口普查的信息。他借用了纺织业的 一个主意:从 19 世纪初起,纺织业就一直 用带孔的大卡片来控制自动化织机。 霍勒瑞斯改造了这个想法,开发出一套 把普查数据存放在穿孔卡片上的系统, 一人一张卡。霍勒瑞斯把卡片的尺寸设计成 与美国纸币(*)相同,这让他能够使用 现成的货币处理设备——比如分拣箱—— 来处理这些卡片。这些卡片后来被称为 穿孔卡片(PUNCH CARDS),有 20 列, 后来又扩展到 45 列。 * 脚注 1862 年,美国政府发行了它的第一张 纸币,一张 1 美元的钞票,尺寸为 73/8 英寸 x 31/8 英寸。这就是霍勒瑞斯时代纸币的尺寸, 也因此是他那些穿孔卡片的尺寸。1929 年, 政府把纸币的尺寸缩小了约 20%,变为 61/8 英寸 x 25/8 英寸,也就是今天使用的尺寸。 霍勒瑞斯的系统证明极为实用,以至于 1896 年他创办了制表机器公司(Tabulating Machine Company,TMC)来制造自己的机器。 1911 年,TMC 与另外两家公司——美国计算 天平公司(Computing Scale Company of America)和国际计时记录公司(International Time Recording Company)——合并,组成计算 制表记录公司(Computing Tabulating Recording Company,CTR)。除了制表机和穿孔卡片,CTR 还制造商用台秤、工业计时器以及切肉片和 切奶酪片机。1924 年,CTR 正式改名为国际 商业机器公司(International Business Machines,IBM)。 到 1929 年,IBM 的技术已经进步到能够 增加穿孔卡片的列数。借助这项新技术, IBM 的穿孔卡片——你还记得,它的大小与 旧时的一元纸币相同——恰好大到可以容纳 80 列,每列可以存储一个字符。因此,当 20 世纪 50 年代末第一批 IBM 计算机被 开发出来时,它们使用的穿孔卡片每张存储 80 个字符。结果,程序和数据就以 80 字符 一行的方式存储,而且在很短的时间内, 这就成了事实上的标准。 到 20 世纪 80 年代,随着程序员开始使用 终端、后来又使用个人计算机,穿孔卡片 逐渐退出舞台。然而 80 字符的标准延续了 下来,因为终端和 PC 都使用每行显示 80 个 字符的屏幕,而这正是程序员(和程序)所 习惯的。Unix 就是在那个时代发展起来的, 所以 80 字符一行被纳入 Unix 文化,只是 顺理成章的事。 现在你明白了,为什么——在 25 年后的 今天,尽管图形用户界面已经如此流行—— 80 字符一行仍然活在 Unix 世界的 各个角落。
相关过滤器:fold、pr fmt 程序格式化段落。它的目标是把一个 段落里的各行连接起来,使段落尽可能短、 尽可能紧凑,同时不改变内容和空白。 换句话说,fmt 让文本看起来整洁漂亮。 语法是: fmt [-su] [-w width] [file...] 其中 width 是行的最大宽度,file 是一个文件的文件名。 fmt 读取文本时,假定段落之间由空行 分隔。因此一个"段落"就是一段或多段 连续的行,其中不含空行。fmt 程序的 工作方式是每次读取并格式化一个段落, 遵循下列规则: • 行宽:让每一行尽可能长,但不要超过某个指定长度。默认的最大行宽是 75 个字符,你可以用 -w 选项改变它。为此,-w 后面跟你要的行宽,例如 -w 50。 • 句子:尽可能在句末断行。避免在句子的第一个单词之后或最后一个单词之前断行。 • 空白:保留所有缩进、单词之间的空格和空行。可以用 -u 选项改变这一点(见下文)。 • 制表符:读取文本时把所有制表符展开为空格,并在最终输出中酌情插入新的制表符。 举个例子,假设你有一个名为 secret-raw 的文件,包含以下三段文字。注意这些行 排得并不整齐: |
|
As we all know, real
|
|
你想按 50 个字符的行长格式化这段文字, 并把结果保存到名为 secret-formatted 的文件。为此使用的命令是: fmt -w 50 secret-raw > secret-formatted secret-formatted 的内容现在如下:
As we all know, real success comes slowly and is
* 脚注 这个例子取自一篇题为 "The Secret of My Success"(我成功的秘诀)的文章。如果你想读 全文,可以在我的网站 上找到。 fmt 程序还有其他几个选项,但只有两个 重要。-u(统一间距)选项告诉 fmt 减少空白,使单词之间不超过一个空格, 句末不超过两个空格,这种风格叫做法语 间距(FRENCH SPACING)(*),例如,文件 joke 包含以下文本: * 脚注 在法语间距中,句子后面跟两个空格而不是 一个。这种风格一般用在等宽字体上,也就是 所有字符宽度都相同的字体。用这类字体时, 句末多一个空格有助于眼睛分辨。 Unix 的两个主要文本编辑器 vi 和 Emacs 都识别法语间距,这使它们能够判断 句子的起止。这也让 vi 和 Emacs 可以把 句子当作完整的单位来处理。例如,你可以 删除两个句子、修改一个句子、向前跳三个 句子,等等。因此许多 Unix 用户养成了在 句后打两个空格的习惯。(我就是如此, 即使键入电子邮件也一样。) |
|
A man walks into a drug store and goes up to the
|
|
你这样格式化它: fmt -u -w 50 joke 输出是:
A man walks into a drug store and goes up to the
注意第一句句末只有一个空格。这是因为 原文件里就只有一个空格,而 fmt 不会 添加空格,它只删除空格。 最后一个选项 -s(只拆分)告诉 fmt 拆开长行,但不合并短行。当你处理的文本 有你想保留的格式时——例如你在写计算机 程序时——就用这个选项。
接下来两节我们要讨论 pr,一个诞生于 Unix 早期(20 世纪 70 年代)的程序。那个 年代打印机贵得几乎没有人能自己拥有一台, 而 pr 就是为在共享环境中准备打印 文件而设计的。不过你会看到,且不说打印, pr 还有一些重要功能,单就格式化文本 而言它们本身就很有用。 不过在讲这些话题之前,我想先花点时间 打好基础,描述一下在 Unix 早期打印文件 是什么样子。 因为打印机很贵,它们几乎总是由一群人 共用。每当用户想打印一个文件,他就会在 终端上键入合适的命令来格式化并打印该 文件。或者他可能运行某个产生打印输出的 程序。每一次打印请求都叫做一个打印作业 (print job),而每产生一个打印作业,它就 被放入打印队列(print queue)里排队。就这样, 一个又一个打印作业被产生、存储,并最终 打印出来。 打印机本体放在机房里,那是一个许多人 ——通常是程序员——共用的公共区域。输出 打印在连续的折叠电脑纸带上,单个打印 作业的输出叫做一份打印件(printout)。随着 打印件越堆越多,就会有人——常是机房里的 操作员(operator)——沿着纸边的孔洞线把纸 撕开,将打印件一份份分开。然后他把每份 打印件放进一个取件筐,发起打印请求的人 随后会去取走。 由于系统的组织方式,必须有一种办法让 操作员能把一摞打印好的纸分拆成各个 打印作业。pr 程序的设计正是为了同时 满足用户和操作员的需要,它提供两项服务。 第一,pr 把文本格式化成页;第二, pr 确保每一页都有自己的页眉、页边距 和页码。这样,打印输出不仅看起来美观 (对用户而言),而且易于整理(对操作员 而言)。 如今,许多人认为 pr 程序只能用于打印, 这是个误解。没错,pr 仍然能做它被设计 来做的事:准备送往打印机的输出(所以名为 pr)。这仍然是一项有用的功能,我们会 在下一节讨论 pr 的这些方面。 不过,pr 能为你做的远不止把文本分成 一页页、生成页眉、页边距和行号。它能以 几种非常实用的方式格式化文本,尤其是 当你学会把 pr 与 fold 和 fmt 结合起来时。例如,你可以用 pr 把单个 文件的文本排成多栏。你还可以合并多个 文件的文本,让每个文件各占一栏。所以, 等我们讲完 pr 的基本功能,我会向你 展示一些用法:它们与打印毫无关系, 却处处体现着高效与巧妙。 提示 如今大多数人不用基于文本的工具来打印 普通文件。他们用图形工具,比如文字 处理器,能轻松控制格式和分页。 不过,如果你是程序员,你会发现一旦有 这类需要,传统的 Unix 工具(pr、 fmt、nl、fold)在打印源代码 方面表现出色。
相关过滤器:fold、fmt pr 的主要功能是把文本格式化成适合 打印的页面。pr 程序还能把文本格式化 成多栏,以及合并多个文件的文本,这些我们 在下一节讨论。pr 的基本语法如下: pr [-dt] [+beg[:end]] [-h text] [-l n] [-o margin] [-W width...] [file...] 其中 beg 是要格式化的第一页, end 是要格式化的最后一页;text 是页眉中间部分要放的文字;n 是每页 的行数;margin 是左边距的大小;width 是输出的宽度;file 是一个文件的文件名。 常见的做法是把 pr 用作管道的一部分, 在文本送交打印机之前先格式化它。例如, 假设你有一个名为 calculate 的程序, 它产生的数据你想打印出来。下面的管道把 calculate 的输出交给 pr 格式化, 然后交给 lpr 打印。(Unix 中打印文件 的两个主要程序是 lp 和 lpr。) calculate | pr | lpr 这里有一个类似的例子。你想把三个文件的 内容合并、格式化并打印出来: cat data1 data2 data3 | pr | lpr 默认情况下,pr 格式化一页时,会在顶部 插入页眉,左边留出页边距,底部加一个页脚。 页眉和页脚各占五行。左边距和页脚只是用来 占位的,所以是空白。但页眉中间那一行含有 信息:文件最后修改的日期和时间、文件名, 以及页码。(这些细节会因你使用的 pr 版本不同而略有差异。)举个例子,这里是 一个典型的页眉。略去空行,如果你格式化 一个名为 logfile 的文件,你可能会看到: 2008-12-21 10:30 logfile Page 1 默认情况下,pr 假定每页 66 行。这是 因为老式打印机使用 11 英寸的纸,每英寸 打印 6 行。页眉(顶部)和页脚(底部)各占 5 行,于是单倍行距的文本每页还剩 56 行。 pr 创建页面时,从第 1 页开始,按顺序 继续——第 2 页、第 3 页,依此类推——直到 所有数据都格式化完毕。 如果你想测试 pr、看看它是怎么工作的, 一个简单的办法是格式化一个文件,把输出 交给 less(第 21 章)。这让你可以一屏 一屏地查看格式化后的输出。例如,假设你在 上一门课,你写了一篇文章,存在一个名为 essay 的文件里。想看看 pr 会怎么 格式化这段文本,你可以使用: pr essay | less 如果你满意看到的效果,就可以把它送到 打印机: pr essay | lpr 或者,你可以把它保存到一个文件: pr essay > essay-formatted 如果你的文章最初是用文字处理器写的, 它会有很长的行。这是因为文字处理器把 每个段落存成一个长行(*)。这种情况下, 你可以先用 fold -s 或 fmt 恰当地 断开这些行,看哪个更适合你手上的文本: * 脚注 文字处理文档以特殊的二进制格式存储。 然而几乎所有 Unix 过滤器都假定数据是以 文本形式存储的。因此,如果你要用本章里 的 Unix 程序处理文字处理文档,必须先在 文字处理器程序里把文档另存为纯文本。 比如在上面这些例子里,文件 essay 就是一个名为 essay.doc 的文字处理 文档的纯文本版本。
fmt essay | pr | less
几乎每次你都会发现,用 pr 为打印格式化 页面时,默认值就挺好。不过若有需要,你 可以用几个选项来改变它们。最常用的是 -d,它告诉 pr 使用两倍行距。 看下面这个例子,它格式化并打印文本文件 essay。这条简单的管道先用 fmt 格式 化文件的各行。fmt 的输出交给 pr, 在那里被格式化成两倍行距的页面。pr 的输出随后送到打印机: fmt essay | pr -d | lpr 结果是一份漂亮的两倍行距打印稿,适合 用来编辑,或交给你的老师。注意 -d 并不 修改原始文本:它只是指定在把文本格式化 成页面时使用哪种行距。原始文件——这里是 essay——保持不变。 如果你想控制格式化哪些页,使用语法: pr +begin[:end] 其中 begin 是要格式化的第一页, end 是要格式化的最后一页。 例如,要跳过第 1 页和第 2 页——也就是从 第 3 页开始,一直做到文件结尾——使用: fmt essay | pr -d +3 | lpr
要只格式化并打印第 3 到第 6 页,使用: fmt essay | pr -d +3:6 | lpr 如果你想指定页眉中间部分的文字,使用 -h 选项,例如: fmt essay | pr -h "My Essay by Harley" | lpr 要改变每页的总行数,使用 -l,后面跟 一个数字。例如,假设你希望每页只有 40 行 文本。算上页眉(5 行)和页脚(也是 5 行),你总共需要每页 50 行: fmt essay | pr -l 50 | lpr 要去掉页眉,使用 -t 选项。使用 -t 时,页与页之间不会有分隔,也就是说所有行 都用于文本。当你在格式化不打算打印的文本 时,这很有用。例如,你可能想把单倍行距的 文本变成两倍行距。下面这条命令格式化 essay 的内容,两倍行距、无页眉,并把 输出保存到名为 essay-double-spaced 的 文件: fmt essay | pr -t -d > essay-double-spaced 默认情况下,pr 不插入左边距。这没问题, 因为很可能你的打印机已经设置为自动留出 页边距。不过,如果你想自己再加一点左边距, 使用 -o(offset,偏移)选项,后面跟额外 页边距的空格数。此外,你可以用 -W 选项 改变输出的宽度(默认为 72 个字符)。 (注意是大写的 W。)使用 -W 时, 过长的行会被截断,所以你要小心别丢失文本。 下面是一个非常实用的例子,说明你可能怎么 使用这两个选项。 如果你是学生,你知道有时候你需要让文章 显得长一点。例如,你有一篇 8 页的文章, 但老师要求交 10 页。当然,你可以重新整理 笔记、做更多研究,把文章重写一遍。或者, 你也可以干脆把文章按两倍行距、宽页边距 打印出来。(*) * 脚注 这话可不是我在这儿说的。 下面的例子格式化文件 essay 的内容, 使用两倍行距的页面、50 个字符的行宽和 5 个空格的左边距;也就是说,每行 45 个字符 的文本。结果是这篇文章看起来比实际的 体量大得多: fmt -w 45 essay | pr -d -o 5 -W 50 | lpr 如果你想在打印前检查输出,使用: fmt -w 45 essay | pr -d -o 5 -W 50 | less 注意:必须给 fmt 使用 -w 45 选项, 因为默认情况下 fmt 产出的行有 72 个 字符长。而在这个例子里,我们要求 pr 把输出限制为每行 45 个字符的文本。所以我 们需要确保没有任何一行超过 45 个字符: 否则它们会被截断。作为 fmt 的替代, 你可以使用 fold -s 程序,它给出类似的 格式: fold -s -w 45 essay | pr -d -o 5 -W 50 | lpr 提示 fold 和 fmt 都可以用来格式化文本 行。你怎么知道该用哪个? fold 程序只做一件事:断行。默认情况 下,fold 在指定的列断行。但有了 -s 选项,fold 会在单词之间断行。 这让文本右边有点参差,但保住了单词的 完整。 fmt 程序格式化段落。和 fold 一样, fmt 会拆开长行。但与 fold 不同, fmt 还会把短行合并起来。 因此,如果你需要在某个特定位置断行,就用 fold。如果你需要格式化含有短行的 文本,就用 fmt。这一点很清楚。 但如果你需要在单词边界处断行,而文本本来 已经格式化好了呢?这种情况下,fold -s 和 fmt 都可以用。注意:这两条命令有时 产生的输出会略有不同,所以两种都试试, 看哪个更适合你手上的数据。 提示 当你把文本交给 pr 之前先用 fold 或 fmt 预处理时,最好明确指定你想要的 行宽,因为这三个程序的默认值各不相同:
• fold:每行 80 个字符
正如我们在上一节讨论的,pr 的用途是把文本 格式化成分页的形式,便于打印。除了我们已经讨论过的 简单格式化之外,pr 还可以把文本格式化成 多栏。输入数据可以来自单个文件,也可以来自 几个文件。 当你用 pr 生成多栏时,语法如下: pr [-mt] [-columns] [-l lines] [-W width] [file...] 其中 columns 是输出栏目的数量; lines 是每页的行数;width 是输出的宽度;file 是某个 文件的名字。 我们先从单个文件开始。要指定输出的栏数,用一个 -(连字符)字符后面跟一个数字。例如, 要两栏,就用 -2。要控制每一栏的长度, 使用 -l 选项。下面是一个典型的例子。 你是一所规模不大但声名卓著的博雅学院的大学生。 你的学业导师认为,如果你参加课外活动,被医学院 录取的机会会更大,于是你加入了反蛋黄酱协会 (Anti-Mayonnaise Society)。作为你职责的一部分, 你同意为协会的通讯简报做事。你刚刚写完一篇文章, 论述蛋黄酱为什么不好。你已经把这篇文章以纯文本 的形式保存到一个名为 article 的文件里。 在你把这篇文章导入简报排版程序之前,你需要把 文本格式化成两栏,每页 48 行。下面这条 管道可以完成这项工作。注意,这里先用 fmt 把文本格式化成 35 字符一行,然后再交给 pr: fmt -w 35 article | pr -2 -l 48 > article-columns 你所需要的排版好的文本,现在保存在一个名为 article-columns 的文件里。 35 这个数字是怎么来的?使用 pr 时,默认 的行宽是 72。两栏中的每一栏末尾都至少有一个空格, 所以最多还剩下 70 个字符可以放文本。把这个数除以 二,得到每栏最多 35 个字符。(如果你用 -W 选项改变行宽,你的计算也要相应地改变。) 提示 把文本格式化成多栏时,pr 会不加 区分地截断过长的行。因此,如果你的文本里有比 栏宽更长的行,你必须在把文本交给 pr 之前,先用 fold -s 或 fmt 把这些行断开。 默认情况下,pr 用制表符而不是空格来 对齐各栏。如果你更希望用空格,那么你要做的只是 使用 expand 程序(本章前面讲过)把 制表符转换成空格。例如,下面这条命令行把 pr 的输出用管道交给 expand, 然后再保存数据: fmt -w 35 article | pr -2 -l 40 | expand > article-columns 如果你仔细检查这条命令的输出,就会看到对齐是靠 空格而不是制表符维持的。(关于如何查看文本中的 制表符和空格,参见本章前面的讨论。) pr 最后一种用途是把多个文件格式化到 各自独立的栏中。使用 -m(merge,合并) 选项,pr 就会把每个文件输出到自己 的一栏里。例如,要把三个文件格式化到三个分栏中, 使用: pr -m file1 file2 file3 用这种方式格式化三个文件时,默认每栏的最大宽度 是 23 个字符。(*) 如果输入文件里有比栏宽更长的 行,pr 会把这些行截断。要避免这一点, 你必须在把文本交给 pr 之前先格式化文本。 下面是一个例子。 * 脚注 默认的行宽是 72 个字符。每一栏的末尾都至少有 一个空格。因为我们要生成三栏,所以从 72 减去 3,得到最多 69 个字符的文本。再除以 3,得到 每栏最多 23 个字符。 你正在筹备一份简报,写了三篇新闻报道,分别保存在 n1、n2 和 n3 这三个文件 里。你想用 pr 把这些报道格式化成三栏, 一篇报道一栏,全部排在同一页上。在使用 pr 之前,你必须先用 fold -s 或 fmt 格式化文本,使任何一行都不超过 23 个字符。下面的 命令完成这项工作,并把输出保存在 f1、 f2 和 f3 三个文件里:
fmt -w 23 n1 > f1
现在你可以用 pr 把这三篇文章各自排在 自己的一栏里: pr -m f1 f2 f3 > formatted-articles 用这种方式合并多个文件时,加上 -t 去掉 页眉往往很方便: pr -mt f1 f2 f3 > formatted-articles 这样你就会得到又长又连贯的栏目,中间不会被打断。
复习问题 #1: wc 程序的三个主要选项是什么? 每一个各有什么作用? wc 的什么特点使它成为如此 有用的工具? 当你使用 wc 时,"行"是怎么 定义的? 复习问题 #2: 在 Unix 中使用制表符时,默认的制表位 在什么位置? 复习问题 #3: fold、fmt 和 pr 这三个程序都可以用来重新格式化文本。它们之间的 主要区别是什么? 复习问题 #4: fold、fmt 和 pr 这三个程序的默认行长各不相同。分别是什么? 应用你的知识 #1: 使用命令 less /etc/passwd 查看你系统上的 密码文件。注意,这个文件每个 userid 一行,而且 每一行包含若干由 : 字符分隔的字段。第一个 字段就是 userid。构造一条管道,生成你系统上所有 userid 的排好序的、带编号的列表。提示:先用 cut(第 17 章),再用 sort,最后 用 nl。 应用你的知识 #2: ls 命令会显示你的工作目录中所有文件的 列表(隐藏文件除外)。构造一条管道来统计文件的 个数。提示:用 ls,再用带有合适选项的 wc。 接下来,构造一条命令,输出类似下面的内容 (其中 xx 是文件的个数): I have xx files in my working directory. 提示:使用 echo(第 12 章)配合命令 替换(第 13 章),并利用你刚刚构造的那条管道。 应用你的知识 #3: 打开你选定的某个网站,复制一些文本到剪贴板。 在 Unix 命令行上,使用 cat 程序创建一个 名为 webtext 的文件: cat > webtext 把文本粘贴进去,然后按下 ^D。(复制和 粘贴在第 6 章讨论过。)现在你就得到了一个包含 该网站文本的文件。 构造一条管道,把这段文本格式化成 40 个字符一行, 并把多个空格改成单个空格。在管道的末尾,一次 一屏地显示这段文本。 应用你的知识 #4: 使用上一个例子中的 webtext 文件,把文本 格式化成适合打印的两栏页面。每一栏宽 30 个字符, 每页 20 行。栏目要用空格而不是制表符来生成。 一次一屏地显示格式化后的输出。 一旦你确认输出是正确的,就把结果保存到一个名为 columns 的文件里。 进一步思考 #1: 正如我们在本章所讨论的,80 列一行的做法在计算 世界里被广泛使用,因为在 20 世纪 50 年代,最早的 IBM 计算机使用打孔卡片,而每张卡片可以容纳 80 个 字符。这个数字 80 纯属巧合,因为打孔卡片的尺寸 取自旧版美元纸币的尺寸。这就是旧技术如何影响新 技术的一个例子。 以类似的方式,1981 年 IBM 推出 PC 时,键盘的 设计基于标准的打字机,而打字机使用的是所谓的 QWERTY 布局(名字来自左上角的六个键)。人们普遍 认为 QWERTY 布局很糟糕,因为最重要的键位置特别 别扭。尽管存在好得多的键盘布局,QWERTY 键盘至今 仍然是标准。 你认为为什么旧技术对新技术会有如此强烈的影响? 为什么这是坏事?为什么这是好事? (花一点时间查一下 Dvorak 键盘的布局,它是 QWERTY 的一个聪明的替代方案。我用 Dvorak 键盘 已经很多年了,绝不会换回去。)
© 本书全部内容,2026 年版权所有,Harley Hahn
|