第21章:格式化输出
本章中,我们继续学习文本相关的工具,关注那些用来格式化文本输出的工具,而非变更文本自身。这些工具经常用来为最终的打印作准备,我们将在下一章学习打印。本章中将覆盖下列程序:
nl
行号fold
按指定长度换行fmt
简单文本格式器pr
准备文本,以便打印printf
格式化和打印数据groff
文档格式系统
简单格式化工具
首先来看一些简单的格式工具。这些都是最单一功能的程序,做着单纯的工作,但是可以用来完成一些小任务,也可以作为管道命令或脚本的一部分。
nl - 行号
nl
程序是一款执行简单任务的相当神秘的工具。它负责编行号。最简单地用法,和 cat -n
类似。
和 cat
一样,nl
可以接收多个文件作为命令行参数或者标准输入。然而,nl
有一些选项,支持初级形式的标记以允许更复杂的编号类型。
nl
在编号时支持一个名为「逻辑页面」的概念。这允许 nl
在编号时重置(重新开始)数字序列。使用选项,可以将起始编号设置成指定值、设置编号的范围及其格式。一个逻辑页面可以进一步分解为页眉、主体、页脚。在每个这样的小节中,行号可以被重置,并且/或者分配一个不同的样式。如果 nl
接收了多个文件,会把这些文件当作一个单一的文本流来对待。文本流中的节,由一些被加到文本中看起来相当奇怪的标记来指示,如表 21-1 所述。
表 21-1:nl 标记
标记
意义
\:\:\:
逻辑页眉的开端
\:\:
逻辑主体的开端
\:
逻辑页脚的开端
表内每个标记元素必须单独出现在其行内。处理玩这些标记元素后,nl
会从文本流中删除这些标记。
表 21-2 列出了 nl
的常用选项。
表 21-2:常用 nl
选项
选项
意义
-b style
将主体编号设置为 style,style 是下列其中之一:
a
= 给所有行编号
t
= 仅给非空白行编号。这是默认值。
n
= 不编号
p
regexp = 仅对匹配基本正则表达式的行编号。
-f style
将页脚编号设置为 style,默认值为 n
(不编号)。
-h style
将页眉编号设置为 style,默认值为 n
(不编号)。
-i number
将页码的增量设置为 number,默认值为 1
。
-n format
设置页码格式为 format,format 是下列选项之一:
ln
= 左对齐,没有前置零。
rn
= 右对齐,没有前置零。这是默认值。
rz
= 右对齐,有前置零。
-p
不在每个逻辑页开端重置页码。
-s string
在每个行号后添加 string
,以创建一个分隔符。默认是一个制表符。
-v number
将每个逻辑页的第一个行号设置为 number
。默认值是 1
。
-w width
将行号区域的宽度设置为 width
。默认值是 6
。
固然,我们很可能不会经常编行号,不过我们可以用 nl
来看一下如何合并多个工具来执行更复杂的任务。我们将以上一章中的工作为基础,创建一个 Linux 发行版报告。因为要用 nl
,所以包含页眉、主体、页脚的标记将很有用。将其加到上一章中的 sed
脚本即可。使用文本编辑器,我们将脚本变更如下,并保存为 distros-nl.sed
文件:
现在的脚本中插入了 nl
逻辑页面标记,并在报告末尾加了一个页脚。注意我们必须把标记里的反斜杠写双份,因为通常情况下会被 sed
解释为转义字符。
接下来,我们合并 sort
和 sed
还有 nl
,生成一个增强版的报告。
这份报告是管道命令的结果。首先,我们按发行版的名称和版本对列表排序(字段 1 和字段 2),然后用 sed
处理排序结果,加入了报表页眉(包含为 nl
的逻辑页面标记)和页脚。最后,用 nl
处理结果,默认情况下,仅对主体逻辑页面部分的文本流编号。
可以重复该命令体验 nl
不同的选项。下面是几个有趣的选项:
和
fold - 按指定长度换行
换行(Folding),是按指定宽度打断文本行的一个进程。和其它命令一样,fold
接收一个或多个文本文件或标准输入。如果给 fold
一个简单的文本流,可以看到它是如何工作的。
这里我们看到 fold
工作了。送至 echo
命令的文本被 -w
选项分解为指定长度。本例中,我们指定一行的宽度为 12 个字符。如果没有指定宽度,默认值是 80 个字符。注意,分解时,是不论单词边界的。额外的 -s
选项会导致 fold
在到达行宽前的最后一个空格处断行。
fmt - 一款简单文本格式器
fmt
程序也是用来折叠文本的,具有更多功能。它接收文件或标准输入,并对文本流执行段落格式化。基本上,它在保留空行和缩进的同时填充并连接文本中的行。
要演示的话,我们需要一些文本。让我们从 fmt
信息页中提取一些。
我们把这段文本复制到文本编辑器中,并保存为 fmt-info.txt
文件。现在我们想要将这段文本重新格式化为 50 字符宽的栏位。我们可以用 fmt
和 -w
选项来处理。
好吧,这是个难堪的结果。或许,我们应该读读这文本,因为它解释了发生了什么。
By default, blank lines, spaces between words, and indentation are preserved in the output; successive input lines with different indentation are not joined; tabs are expanded on input and introduced on output.
默认情况下,空行、单词间的空格、缩进,会在输出中被保留;连续输入的具有不同缩进的行,不会被连接;制表符在输入时被展开,在输出时被引入。
所以,fmt
保留了首行的缩进。幸运的是,fmt
提供了一个选项,可以纠正此项。
这样就更好了。通过添加 -c
选项,我们得到了希望得到的结果。
fmt
有一些有趣的选项,如表 21-3 所述。
表 21-3:fmt
选项
选项
描述
-c
在冠缘(crown margin)模式中操作。保留一个段落中最初两行的缩进。后续行与第二行的缩进对齐。
-p string
仅对字首(prefix)为 string
的行调整格式。调整后,string
的内容会固定在调整格式后的每行之首。该选项可以用在对源代码中的注释部分调整格式。例如,任何编程语言或配置文件会用 #
字符来界定一行注释,就可以指定 -p '# '
来调整格式,这样,就可以仅调整注释的格式了。可以看下方的示例。
-s
仅分割(split-only)模式。此模式中仅分割行以适应指定栏宽。不会连接短行以填充行。此模式适用于调整代码的格式,代码行不需要连接。
-u
执行统一(uniform)空白字符。此选项应用传统「打字机风格」调整文本格式。意味着单词之间保留一个空格,句子之间保留两个空格。该模式有助于移除「对齐」,也就是说,用空格填充以强制在左右边距上对齐的文本。
-w width
将文本调整到 width
字符宽的栏位。width
默认值是 75 字符。注意:实际上 fmt
调整的行宽会略微小于指定宽度,以允许行平衡。
-p
选项特别有趣。通过它,可以选择性调整文件的一部分,只要需要调整格式的行都以相同的字符序列开头。许多编程语言用英镑符号(#
)指示注释的开端,所以就可以用这个选项调整格式。让我们创建一个文件以模拟使用了注释的程序。
示例文件包含了以 #
开头的注释(一个 #
后跟着一个空格),而「代码」则没有。现在,使用 fmt
,可以调整注释的格式,保留代码不变。
可以看到连在一起的注释行还是连着的,空行和没有以指定前缀开端的行,得以保留原样。
pr - 格式化文本以供打印
pr
程序用来给文本分页。当打印文本时,经常需要用几行空白来分隔页面输出,以便为每个页面提供天白和地白。更进一步地,空白区域可以用来插入页眉和页脚。
我们调整 distros.txt
文件的格式,使其成为一系列短小的页面(此处就显示前两页),来演示 pr
。
本例中,我们用 -l
选项(设置页长度)和 -w
选项(设置页宽度)来定义「页面」为 65 列宽和 15 行长。pr
对 distros.txt
文件内容分页,用几行空白分隔每个页面,并创建一个包含文件修改时间、文件名、页码的默认页眉。pr
程序提供许多选项来控制页面输出。我们将在第 22 章「打印」中学习。
printf - 格式化和打印数据
和本章中其它命令不同,printf
命令不能用在管道中(它不能接收标准输入),也不是直接在命令行中使用的常见应用程序(大多数情况用在脚本中)。那它为何很重要呢?因为它的应用是如此广泛。
printf
(源自短语 "print formatted")最初为 C 语言开发并被许多编程语言贯彻实行,包括 shell。实际上在 bash 中,printf
是内建的。
printf
工作起来是这样的:
printf "format" arguments
命令收到一个包含格式描述的字符串,字符串随后被应用到一个参数列表。格式化的结果会被送到标准输出。这里有一个小小的示例:
格式字符串包含字面值(I formatted the string
)、转义序列(\n
)和以 %
字符开始的转换规范(conversion specifications)。上面的例子中,转换规范 %s
用来格式化字符串 foo
并作为命令的输出。还有个例子:
可以看到,%s
转换规范在命令行输出中,被字符串 foo
取代。s
转换用来格式化字符串数据。还有其它标识符供其它类型的数据使用。表 21-4 列出了常用的数据类型。
表 21-4:常用 printf
数据类型标识符
标识符
描述
d
将数字格式化为有符号十进制整数。
f
格式化一个浮点数。
o
将数字格式化为一个八进制数。
s
格式化一个字符串。
x
格式化一个数字为十六进制数,使用小写的 a
- f
。
X
与 x
一样,只不过使用了大写字母。
%
打印一个字面值 %
符号(意即,使用 %%
)
我们将用每个转换标识对字符串 380
作一演示。
因为我们指定了六个转换标识,所以也必须提供六个参数给 printf
去处理。六个结果显示了每个标识的效果。
几个可选组件可以加在转换标识后以调整其输出。一个完整的转换规范可以由下列部分组成:
%[flags][width][.pricision]conversion_specification
使用时,多个可选组件必须按照先前指定的顺序出现才能被正确解释。表 21-5 描述了每个组件。
表 21-5:printf
转换规范组件
组件
描述
flags
有五个不同的 flags:
#
:使用「替代格式」输出。随数据类型而变。对于 o
(八进制数字)转换,输出会以 0
为前缀。对于 x
和 X
(十六进制数字)转换,会分别以 0x
和 0X
为前缀输出。
0
(零):用零填充输出。意思是,该字段会以前置零填充,如 000380
。
-
(短横):左对齐输出。默认情况下,printf
是右对齐输出的。
''
(空字符串):为正数制造一个前置空格。
+
(加号):为正数加符号。默认情况下 printf
仅为负数加符号。
width
一个数字,指定最小字段宽度。
.precision
对浮点数,指定输出小数点后的数字的精度。对字符串转换,精度指定要输出的字符的个数。
表 21-6 列出了一些不同格式的示例。
表 21-6:printf
转换规范示例
参数
格式
结果
备注
380
"%d"
380
整数的简单格式。
380
"%#x"
0x17c
将整数调整为使用「替代格式」标识的十六进制整数。
380
"%05d"
00380
调整为带前置零(扩充)的整数,最小字段宽度为 5 字符。
380
"%05.5f"
380.00000
调整数字格式为带扩充及五位小数精度的浮点数。因为指定最小字段宽度(5
)小于实际格式化后的数字的宽度,所以扩充没有显示效果。
380
"%010.5f"
0380.00000
通过增加最小字段宽度为 10
,已经可见扩充的效果了。
380
"%+d"
+380
+
标识为正数加了符号。
380
"%-d"
380
-
是格式化后的数字左对齐。
abcdefghijk
"%5s"
abcedfghijk
带最小字段宽度的字符串。
abcdefghijk
"%.5s"
abcde
为字符串应用精度,导致字符串被截断。
还有,printf
最常用于格式化表格数据的脚本中,而非直接用在命令行中。但是我们还是可以展示它是如何被用在解决变化多端的格式化问题的。首先,输出一些由制表符分隔的字段。
通过插入 \t
(制表符的转义序列),我们达到了想要的效果。下面是一个整齐格式的数字:
这里显示了最小字段宽度对字段宽度的影响。该怎样格式化一个微型网页呢?
文档格式系统
目前为止,我们已经学习了简单文本格式化工具。这些对于小型简单的任务很有效,但是如果是更大的任务呢?Unix 系统在技术和科学家用户中广泛流行的一个原因(除了为所有软件开发者提供了一套强大的多任务多用户环境之外),就是其提供了可以制作多种类型文档的工具,特别是科学和学术出版物。实际上,如 GNU 文档所描述,文档准备有助于 Unix 的发展。
Unix 的第一个版本是在贝尔实验室中的 PDP-7 上开发的。1971 年,开发者想要得到一台 PDP-11,以便进一步开发操作系统。为了证明该系统的成本合理,他们建议为 AT&T 专利部门实施一个文档格式化系统。第一个格式化程序是一个由 J. F. Ossanna 编写的对 McIllroy 的 roff 的重新实现。
文档格式化领域由两个主要家族统治:源自 roff
程序、包括 nroff
和 troff
,和那些基于 Donald Knuth 的 TEX(读作 tek)排版系统。对,中间那个下沉的 E 是其名字的一部分。
roff
这个名字来源于术语 run off(快速制作),一如 "I'll run off a copy for you." 中的意义一般。nroff
程序为使用等宽字体输出到设备的文档调整格式,如字符终端和打字机风格的打印机。在其推出时,囊括了几乎所有连接到计算机的打印设备。更晚些的 troff
程序为输出在排版机(typesetters)上的文档调整格式,排版机是为商业化印刷生产「照相就绪」类型的设备。今天,大多数打印机可以模拟排版机的输出。roff
家族还包含了其它用于预备部分文档的程序。包括 eqn
(供数学方程式排版用)和 tbl
(供表格排版用)。
TEX 系统(稳定形式)的首次发布是在 1989 年,某种程度上取代了 troff
作为排版机输出的首选工具。这里不会学习 TEX,因为其过于复杂(关于它,有一整本书)且并非大多数现代 Linux 系统上所默认安装的。
提示:对于那些有兴趣安装 TEX 的,可以从大多数发行版仓库中安装
texlive
和 LyX 图形编辑器。
groff
groff
是一套程序,包含 troff
的 GNU 实现。它还包括了模仿 nroff
和其它 roff
程序家族的脚本。
roff
及其衍生品用来制作格式化文档,不过它们的处理方法对现代用户来说就相当陌生了。今天,大多数文档是由那些同时可以写作和排版文档的字处理程序制作的。在图形字处理程序到来之前,文档都是分两步制作完成的,先用文本编辑器撰写,后用如 troff
的处理器设置格式。通过使用标记语言将格式化程序的指令嵌入到所撰写的文本中。在现代,与该进程类似的,就是网页,以某种方式使用文本编辑器撰写,然后通过网页浏览器渲染,用 HTML 标记语言描述最终的页面输出。
我们不打算完整地学习 groff
,因为其标记语言的许多元素都涉及相当罕见的排版细节。相反,我们会集中在它广泛应用的一个宏包(macro packages)上。这些宏包将许多其低级别命令浓缩进高级别命令,使 groff
更易用。
有那么一刻,让我们考虑一下低调的手册页。它以 gzip
压缩文本的形式呆在 /usr/share/man
目录中。如果我们要检查其未压缩的内容,将会看到下面这行命令(将显示第 1 节中 ls
的手册页) :
比较一下正常显示的手册页,可以看到标记语言及其结果之间的对应关系。
这里,有趣的原因是,手册页是用 groff
渲染的,使用了 mandoc
宏包。实际上,我们可以用下列管道命令模拟出手册页命令:
这里,我们指定了 mandoc
宏包选项,使用 groff
程序,为 ASCII 输出驱动。groff
可以输出多种格式。如不指定格式,缺省输出是 PostScript。
我们在上一章简单介绍了 PostScript,下一章中还会讲到。PostScript 是一个页面描述语言,用来将一个印刷页面的内容描述到一个类排版机设备上。如果我们得到命令的输出,并将其存储为一个文件(假设我们使用一个图形桌面,并有个 Desktop
目录),桌面上会出现一个输出文件的图标。
双击图标,会启动一个页面查看器,显示文件的渲染后的形式。如下图所示:
我们看到的是一个排版精美的 ls
手册页页面!实际上,可以将 PostScript 文件转换为 PDF 文件,命令如下:
ps2pdf
程序是 ghostscript
包的一部分,大多数 Linux 系统上都有安装,以支持打印。
提示:Linux 系统经常包含有很多文件格式转换的命令行程序。它们常以 format2format 的习惯命名。请试着用
ls /usr/bin/*[[:alpha:]]2[[:alpha:]]*
来把它们认出来。还可以尝试检索名为 formattoformat 的程序。
最后一个关于 groff
的练习,我们将重温 distros.txt
文件。这次,我们用 tbl
程序,它用来将发行版列表排版成表格。要做到这一点,我们打算用早先的 sed
脚本加一些标记到我们的文本流中,并将其输送到 groff
。
首先我们修改 sed
脚本,加入tbl
所需的必要的标记元素(在 groff
中叫作请求 requests)。使用文本编辑器,我们变更 distros.sed
文件如下:
记住,为了让脚本能正常工作,必须保证 Name Version Released
三个词是由制表符而非由空格分隔的。我们把结果保存为 distros-tbl.sed
文件。tbl
使用 .TS
和 .TE
两个请求来开始和结束表格。.TS
请求后的一行,定义了表格的全局属性,例如,是水平居中和由一个盒子围绕着。其余几行的定义,描述了表格中每行的输出布局。现在,如果我们再次用新的 sed
脚本运行报表生成管道命令,会得到如下结果:
在 groff
中加入 -t
选项,指示其用 tbl
处理文本流。类似的,-T
选项用 ASCII 输出而非默认的 PostScript。
这个输出格式是我们可期待的最佳格式,如果我们受限于终端屏幕伙食打字机风格的打印机的话。如果我们知道 PostScript 输出且用图形查看输出的话,我们可以得到更理想的结果,如下图。
总结
鉴于文本对于类 Unix 操作系统的特性至关重要,因此有许多工具可用于操作和格式化文本。正如我们所已见的那般!fmt
和 pr
这类简单的格式化工具能在脚本中发挥作用,以生成短小的文档,而 groff
(及其同类)则可以写一本书了。我们可能永远不会用命令行工具写一本技术书(尽管还有很多人就是这么做的!),但是我们知道我们可以这么做。
扩展阅读
还有,维基百科上的诸多文章:
Last updated
Was this helpful?