在Linux的世界里,我们不仅仅是和冷冰冰的机器打交道,更是在和大量的文本文件进行着对话。有时候,这些文本就像一匹未经雕琢的野马,虽然充满了原始的力量,但却显得有些杂乱无章。 为了让这些文本信息更加清晰、易读,甚至是为了让它们能够以优美的姿态出现在打印纸上,我们就需要学习一些给文本“穿上新衣”的技巧——也就是格式化输出。

这一部分,我们将一起探索几个神奇的命令行工具。它们有的像个裁缝,能把过长的文本折叠整齐;有的像个排版师,能给每一行都加上序号;还有的甚至能像个专业的出版系统,帮你制作出精美的文档。
我们将从简单实用的nl、fold、fmt和pr开始,然后深入了解功能强大的printf,最后还会接触到专业的文档处理系统groff。
nl:给文本加上行号假如你拿到了一份很长的文档,比如一份代码或者一份报告,你想和同事讨论其中的某几行。如果每一行都有一个编号,沟通起来是不是就方便多了?nl 命令就是这样一位细心的助手,它的工作就是为文本的每一行添加上编号。
它最简单的用法就像这样,我们可以用它来显示一个文件的内容,并为每一行加上行号:
|$ nl distros.txt 1 SUSE 10.2 12/07/2006 2 Fedora 10 11/25/2008 3 SUSE 11.0 06/19/2008 4 Ubuntu 8.04 04/24/2008
你可能会说,这和 cat -n 看起来很像啊!的确如此,但 nl 提供了更多的“定制”选项。比如,它默认不会为空白行编号,这在很多情况下非常有用,能让编号集中在有内容的行上。
nl 的真正强大之处在于它能理解文本的“结构”。它引入了“逻辑页面”的概念,可以将文本分为页眉、正文和页脚,并对不同的部分使用不同的编号风格。
nl 还能和其他命令配合,发挥更大的作用。比如,我们可以先用 sort 把一份清单排序,然后用 nl 加上行号,生成一份整洁的报告。
fold:让长长的文本“折叠”起来你有没有遇到过这样的情况:一行文本太长了,在屏幕上显示不全,需要拖动水平滚动条才能看完?fold 命令就像一个折纸高手,能把这些过长的“纸条”按照我们指定的宽度,整整齐齐地“折叠”起来。
比如,我们有一句很长的话,想让它在每12个字符宽度的地方就换行:
|$ echo "The quick brown fox jumped over the lazy dog." | fold -w 12 The quick br own fox jump ed over the lazy dog.
看,fold 很“暴力”,它严格按照12个字符的宽度进行切割,哪怕一个单词被切成两半也在所不惜。这在处理某些固定格式的数据时可能有用,但在阅读文本时就不那么友好了。
幸运的是,fold 也提供了更“温柔”的模式。只要加上 -s 选项,它就会在单词之间的空格处进行折行,尽量保持单词的完整性:
|$ echo "The quick brown fox jumped over the lazy dog." | fold -w 12 -s The quick brown fox jumped over the lazy dog.
这样一来,输出结果是不是就悦目多了?fold 命令虽然简单,但在需要快速整理长行文本,让它们在固定宽度的终端或编辑器里更容易阅读时,它是一个非常方便的小工具。
fmtfold 命令虽然能解决行过长的问题,但它有点“一根筋”,不懂得段落和上下文。这时,我们就需要一位更聪明的文本“造型师”——fmt 命令。
fmt 不仅仅是简单地折叠文本,它会尝试理解文本的段落结构,然后以更自然、更符合阅读习惯的方式重新安排它们。
fmt 会把一个段落里的所有行“合并”起来,然后再根据你指定的宽度重新切分。它会保留段落之间的空行,也会保留段落开头的缩进,非常智能。
让我们来看一个例子。假设我们有一段参差不齐的文本:
|$ cat some_text.txt `fmt` reads from the specified FILE arguments (or standard input if none are given), and writes to standard output. By default, blank lines, spaces between words, and indentation are preserved in the output.
现在我们用 fmt 把它格式化成宽度为50个字符的样式:
|$ fmt -w 50 some_text.txt `fmt` reads from the specified FILE arguments (or standard input if none are given), and writes to standard output. By default, blank lines, spaces between words, and indentation are preserved in the output.
fmt 很好地处理了段落,让文本块看起来整洁多了。它甚至还很贴心地尝试在句子末尾断行,避免在句子开头或结尾的单词后换行,让阅读体验更加流畅。
fmt 还有一个特别实用的功能,尤其对于程序员来说。使用 -p 选项,你可以只格式化那些以特定字符开头的行。比如,在很多编程语言里,注释是以 # 开头的。如果你只想整理代码里的注释,而不动代码本身,就可以这样做:
|$ fmt -p '# ' -w 50 your_script.sh
这个命令会像一个精准的外科医生,只对你的注释“动刀”,而代码部分则秋毫不犯。fmt 就是这样一个既懂得宏观排版,又能在细节上做到精准控制的强大工具。
pr:为打印做好准备在数字时代,我们可能不常需要打印文档,但当你真的需要把一份文本文件变成纸质版时,你一定不希望它看起来乱糟糟的。pr 命令就是一位专业的打印助理,它能将文本格式化,为打印到纸张上做好万全的准备。
它的核心功能是“分页”。想象一下,它会把一长串的文本“卷轴”,切分成一页一页的A4纸。在每一页的顶部,它会自动添加页眉,通常包含文件名、日期和页码,同时在页面底部留出边距。
我们来看一个简单的例子,让 pr 把一个文件按照每页15行的高度进行分页:
|$ pr -l 15 -w 65 distros.txt 2023-10-27 10:30 distros.txt Page 1 SUSE 10.2 12/07/2006 Fedora 10 11/25/2008 SUSE 11.0 06/19/2008 Ubuntu 8.04 04/24/2008 Fedora 8 11/08/2007 2023-10-27 10:30 distros.txt Page 2 SUSE 10.3 10/04/2007 Ubuntu
在这个例子中,我们用 -l 15 定义了每一页的长度(15行)。你可以看到,pr 不仅切分了内容,还自动为我们生成了页眉。
它还有很多选项可以控制页面的布局,比如设置多栏打印、自定义页眉等等。虽然我们今天更多地使用图形界面的打印预览,但了解 pr 能让我们明白,早在图形界面普及之前,Linux就已经拥有了强大的文档处理能力。
printf在我们之前接触的命令中,它们大多是“接收”文本,然后进行处理。而 printf 命令则恰恰相反,它是一个文本的“创造者”。
它的名字来源于C语言中的一个同名函数,意思是“格式化打印”。你可以把它想象成一个功能极其强大的“瑞士军刀”,能够按照你的精确要求,创造出任何你想要的文本格式。
和其他命令不同,printf 很少用于处理通过管道传来的数据流。它更像是一个在脚本编程中大放异彩的工具,用来生成格式严谨、高度定制化的输出。
printf 的工作模式是“模板 + 数据”。你先提供一个“格式字符串”(模板),里面包含你希望看到的字面文本,以及一些特殊的“占位符”。然后,你再提供与占位符对应的数据。printf 会把数据“填”到占位符里,生成最终的字符串。
它的基本语法是这样的: printf "格式字符串" 参数1 参数2 ...
最常用的占位符是以 % 开始的转换说明。我们来看一个例子:
|$ printf "你好, %s! 今天是星期%d。\n" "小明" 5 你好, 小明! 今天是星期5。
在这个例子里,%s 是一个字符串占位符,printf 用后面的第一个参数 "小明" 替换了它。%d 是一个整数占位符,被第二个参数 5 替换了。最后的 \n 是一个特殊的转义字符,代表“换行”,这样光标就会移动到下一行的开头。
如果没有它,输出的末尾就不会有换行。
printf 支持多种不同数据类型的占位符,下面是一些最常用的:
除了简单的占位符,printf 还允许你控制输出的宽度、对齐方式和小数精度,这在制作报表或需要对齐的文本列时非常有用。例如,%10s 会保证字符串占据至少10个字符的宽度(如果不够会用空格补齐),而 %.2f 则会让一个小数只显示两位小数。
目前为止,我们看到的工具都像是文本的“化妆师”,做的是一些表面的修饰工作。但接下来我们要认识的 groff,则是一位真正的“建筑大师”。
它不是简单地修饰文本,而是能够根据一张“图纸”,从无到有地构建出一整份结构精美、版式复杂的文档,比如一本书、一篇学术论文或者一份官方手册。
groff你可能很难想象,推动早期Unix发展的强大动力之一,竟然是为了方便地排版专利文档!当时的程序员们开发了一套名为 roff 的程序("run off"的缩写,意为“打印一份副本”),它开创了一种全新的文档处理模式。
这种模式的核心思想是“内容与样式分离”。作者在撰写时,只需要用纯文本编辑器,专注于内容创作。当需要设置格式时,比如标题、加粗或列表,就在文本中插入一些简单的“标记”(markup)。
最后,再用 groff 这样的处理程序来读取这份带标记的文本,并根据标记的指令,渲染出最终漂亮的文档。
这个概念听起来是不是很熟悉?没错,我们今天浏览网页的原理和它如出一辙!我们用HTML(一种标记语言)来组织网页内容,然后浏览器(一个处理程序)负责将HTML渲染成我们看到的五彩斑斓的页面。
groff 就是命令行世界里的“浏览器”,而它所使用的标记语言,就是troff的语法。groff 是 troff 的GNU实现版本,功能更加强大,至今仍在Linux世界中扮演着不可或缺的角色。
groff实战groff 最广为人知的应用,就是我们每天可能都会用到的 man 命令。你有没有想过,man ls 那样格式清晰、段落分明的帮助文档是怎么来的?它的“源代码”其实就是一个包含了 groff 标记的纯文本文件。
我们可以亲眼看一下 ls 命令man手册的“源码”:
|$ zcat /usr/share/man/man1/ls.1.gz | head ." DO NOT MODIFY THIS FILE! It was generated by help2man 1.35. .TH LS "1" "April 2008" "GNU coreutils 6.10" "User Commands" .SH NAME ls \- list directory contents .SH SYNOPSIS .B ls [\fIOPTION\fR]... [\fIFILE\fR]...
我们能看到里面充满了.TH, .SH, .B 这样以点开头的奇怪标记。这些就是给 groff 的指令。.SH NAME 告诉 groff:“这里是一个名为‘NAME’的大标题”。
当我们执行 man ls 时,背后其实就是 groff 在辛勤地工作,把这些指令翻译成我们看到的漂亮格式。我们甚至可以手动模拟这个过程:
|$ zcat /usr/share/man/man1/ls.1.gz | groff -mandoc -T ascii | head LS(1) User Commands LS(1) NAME ls - list directory contents SYNOPSIS ls [OPTION]... [FILE]...
除了在终端显示,groff 还能生成打印质量的PostScript (.ps)文件,我们甚至可以再用 ps2pdf 命令把它转换成更通用的PDF文件。
groff 的能力远不止于此。通过和它的伙伴 tbl(一个专门处理表格的预处理器)合作,它还能制作出非常专业的表格。我们仍然可以用之前的发行版列表来举例,通过一个包含了 tbl 指令的脚本,我们可以生成一个带有边框、居中对齐的精美表格:
|$ # 这一步的背后,是一个脚本将数据转换为了groff能理解的表格标记 $ sort -k 1,1 -k 2n distros.txt | sed -f distros-tbl.sed | groff -t -T ascii +------------------------------+ | Linux Distributions Report | +------------------------------+ | Name Version Released | +------------------------------+ | Fedora 5 2006-03-20| | Fedora 6
虽然我们今天有了很多图形化的文字处理软件,但 groff 的存在告诉我们,在Linux的命令行里,蕴藏着足以完成任何专业级排版任务的强大力量。