Unix 之书
☰ 目录

第 21 章...

显示文件 (Displaying Files)

我们花这么多时间使用计算机,重要的是要提醒自己: 我们努力的主要成果,几乎总是某种输出:文本、 数字、图形、声音、照片、视频,或者其他数据。 当你使用本书讨论的 Unix 命令行程序时, 输出通常是文本,要么在生成的同时显示在 显示器上,要么保存在文件里。

因此,Unix 一直都有各种各样的程序,让你能够 显示文本数据,数据可以来自某个程序的输出, 也可以来自一个文件。本章我们将讨论用来 显示文件内容的程序。我们先从文本文件讲起, 然后再讨论二进制文件。

在整个讨论过程中,我对你的期望有两点。 第一,每当你需要显示文件中的数据时,我希望你 能够分析情况,选出最适合完成这项任务的程序。 第二,无论你决定使用哪个程序,我都希望你对它 足够熟悉,能胜任日常的大多数任务。

为了开始讨论,我先带你概览一遍 Unix 中用来显示文件的程序。我会逐一 介绍每个程序,说明它做什么、 什么时候该用它。之后我们再依次 详细讨论每个程序,那时我会把 细节补齐。在这些程序里,最重要的 无疑是 less。(这个名字的 由来我稍后解释。)正因如此,我们会 在这个既实用又好用的程序上 花最多的时间。

在讨论各个程序的过程中,我们会有两次 小小的绕行,去讲两个有趣的话题。第一,我要 介绍基于文本的程序处理你输入的两两种方式: "raw mode"(原始模式)和 "cooked mode" (规范模式)。第二,我要向你介绍二进制、 八进制和十六进制记数法,在显示二进制文件时 你必须理解这些概念。

本章我们会一直讨论如何显示 "文件",尽管严格说来,我还没有解释 文件到底是什么。在第 23 章,我们会详细 讨论 Unix 文件系统,那时我会给出 Unix 文件的准确技术定义。目前,我们只需凭直觉 理解:文件是有名字、装着信息的东西。 例如,你可能会显示一个名为 essay 的文件,里面是你写的一篇文章的正文。

 

开始之前还有一个概念要说:当人们说 "显示"一个文件时,指的是显示文件的 内容。例如,如果我说"下面的命令 显示 essay 文件",意思是"下面的命令 显示 essay 这个文件的内容"。 这一点虽然微妙,但很重要,所以 一定要弄清楚。

用于显示文件的程序概览
(Survey of Programs Used to Display Files)

Unix 有各种各样的程序可以用来显示 文件。本节我们先概览这些程序, 让你对可用的工具整体有个 印象。本章后面我们会再逐个 详细讨论。

先说一类程序,它们唯一的目的就是 一次一屏地显示文本数据。这样的程序 称为分页器(PAGER)。这个名字的由来是: 在 Unix 早期,用户用的是把输出 打印在纸上的终端。因此,要看一个 文件,你得把它一页一页地打印到纸上。 如今,看文件当然是把内容一屏 一屏地显示在显示器上。但这些 干活的程序仍然叫作 "pagers"(分页器)。

一般来说,使用分页器有两种方式。 第一,正如我们在第 15 章讨论的,你可以在 管道末尾使用分页器来显示另一个 程序的输出。前面各章我们已经见过 很多这样的例子,例如:

cat newnames oldnames | grep Harley | sort | less
colrm 14 30 < students | less

在第一条管道中,我们把两个文件的 内容合并起来,用 grep 从中挑出所有 包含字符串 "Harley" 的行,再把结果交给 less 显示。第二个例子中,我们从一个 文件读取数据,删掉每行数据的第 14 到 30 列,同样把结果交给 less 显示。

使用分页器的另一种方式是让它 一屏一屏地显示某个文件的内容。 例如,下面的命令用 less 查看 Unix 口令文件(第 11 章有介绍):

less /etc/passwd

用这种方式你可以查看任何文本文件, 只要键入 less,后面跟上文件名即可。 (选项、语法和其他细节我们本章 后面会讲。)

虽然 less 是 Unix 的主要 分页器,你可能还听说过另外两个 同类程序:morepg。 还记得我们在第 2 章讨论过的 内容吗:20 世纪 80 年代,Unix 有 两大分支:AT&T 开发的 System V 和加州大学伯克利分校开发的 BSD。 pg 是 System V 的默认分页器; more 是 BSD 的默认分页器。 如今,这两个程序都已被 less 取代而过时了。

偶尔你会不得不用到 more。 因此我们会简单谈谈它,这样万一你 遇到它,就知道该怎么做。pg 程序 大体上已经入土为安,我们没有必要 讨论它。我这里只是出于历史原因 提一下:如果你看到这个名字,至少知道 它是什么。

除了使用分页器,你还可以用 cat 程序来显示文件。正如我们在 第 16 章讨论的,cat 的主要用途是把 多个文件的内容合并起来。不过, cat 也可以用来快速地显示一个文件, 例如:

cat /etc/passwd

因为 cat 一次显示整个文件(而不是 一屏一屏地显示),所以只有在文件短到 不用滚动就能放进屏幕时你才该用它。 大多数情况下,用 less 更合适。

多数情况下,当你想看整个文件时会用 lesscat。如果你只想显示文件的 一部分,还有三个程序可用:head 显示 文件的开头;tail 显示文件的结尾; grep 显示所有包含(或不包含)某个 特定模式的行。

在第 16 章,我们讨论了如何把 headtail 作为管道中的过滤器 使用。本章我会演示如何对文件使用 它们。第 19 章我们详细讲了 grep, 第 20 章我给了很多例子。因此本章不必 再讨论 grep。(不过我确实想提 一下它。)

下一类可以用来显示文件的程序是 文本编辑器。文本编辑器让你查看文件 的任意部分、搜索模式、在文件里 来回移动,等等。它还能让你编辑 (修改)文件。所以,当你想同时做 修改,或者想用编辑器特有的命令在 文件里移动时,就用文本编辑器显示 文件;否则就用分页器。

在第 14 章,我提到过几种在 Unix 和 Linux 系统上都能广泛使用的文本 编辑器:keditgedit、Pico、 Nano、vi 和 Emacs。这些编辑器 都能让你显示和修改文件。但毫无疑问, vi 和 Emacs 是最强大的工具 (也是最难学的)。本书中我们要详细 讨论的唯一编辑器是 vi,将在 第 22 章讲解。

有时你会想用文本编辑器去查看一个 极其重要的文件,重要到你不想意外 做出任何改动。为此,你可以以所谓 "只读"(read-only)模式运行编辑器, 意思是你能看这个文件,但不能做任何 修改。

要以只读模式启动 vi,你使用 -R 选项。例如,任何用户都可以查看 Unix 口令文件(第 11 章),但除非你是 超级用户,否则不允许修改它。因此,要 用 vi 查看口令文件而不能编辑它, 你应该这样写:

vi -R /etc/passwd

作为一种方便,你可以把 view 当作 vi -R 的同义命令:

view /etc/passwd

即使你以超级用户身份登录, 你也常常会选用 vi -Rview 来查看非常重要的系统文件。 这能确保你不会意外改动它。(我们在 第 22 章会更多讨论这一点。)

到目前为止我们讨论的程序都是处理 文本文件的,也就是包含一行行字符的 文件。但还有许多不同类型的非文本 文件,称为二进制文件,你偶尔需要 看看这类文件的内部。我要提的最后 两个程序 — hexdumpod — 就是用来显示 包含二进制数据的文件的。

例如,假设你在写一个把二进制输出 发送到文件的程序。每次运行 程序,你都需要查看文件内部以检查 输出。这正是 hexdumpod 派上用场的地方。细节我们本章后面 再谈。作为快速示例,下面两条 Linux 命令中的任意一条都能让你查看 包含 grep 程序的那个文件的 内部。(现在先别管选项。我们 稍后会讨论。)

hexdump -C /bin/grep | less
od -Ax -tx1z /bin/grep | less

作为参考,Figure 21-1 汇总了 我们刚才概览的这些程序。看这份 汇总时,请花点时间感受一下:Unix 中 用于显示文件的工具有多少个,每一个 都有自己的特点和用途。

Figure 21-1: 用于显示文件的程序

Unix 和 Linux 有大量的工具可以用来显示 文件的全部或部分内容。这份汇总列出了 其中最重要的工具,以及讨论它们的章节。 至少,你应该能熟练使用 lesscat headtailgrep 来 显示文本文件。你还应该知道如何使用 vi,因为它是 Unix 的主要文本编辑器。 如果你是程序员,就应该熟悉 hexdumpod 中的至少一个,以便显示 二进制文件。

程序 用途 章节
less分页器:一次显示一屏21
more分页器(已过时,配合 BSD 使用)21
pg分页器(已过时,配合 System V 使用)
cat一次显示整个文件,不分页16
head显示文件的开头部分16, 21
tail显示文件的结尾部分16, 21
grep显示包含/不包含特定模式的行19, 20
vi文本编辑器:显示并编辑文件22
view, vi -R只读文本编辑器:显示文件但不允许修改22
hexdump显示二进制(非文本)文件21
od显示二进制(非文本)文件21

less 简介:
启动、退出与帮助
(Introduction to less:
Starting, Stopping, Help)

less 程序是一个分页器。也就是说, 它一次一屏地显示数据。启动 less 时,有很多选项可供挑选,而它 运行起来之后,又有许多命令可以使用。 不过,你很少需要用到那么复杂的程度。 本章我们专注于你日常会用到的基本 选项和功能。至于我们不会涉及的更 深奥的选项和命令,请看手册页和 Info 页面:

man less
info less

(联机手册和 Info 系统在第 9 章 讨论。)

使用 less 的基本语法如下:

less [-cCEFmMsX ] [+command] [-xtab] [file...]

其中 command 是让 less 自动执行的 命令;tab 是你想使用的制表符间距; file 是文件的名字。

大多数时候,你不需要任何选项。 你要做的只是指定一个或多个要显示的 文件,例如:

less information
less names addresses

只要某个文本文件你有读权限,就可以用 less 来显示它,包括系统文件, 也包括属于其他用户标识的文件。 (我们在第 25 章讨论文件权限。) 举个例子,下面的命令显示一个很有名的 系统文件,也就是我们在第 7 章讨论过的 Termcap 文件:

less /etc/termcap

Termcap 文件包含对各种不同终端类型的 技术描述。虽然 Termcap 大多已被(*)一个 叫作 Terminfo 的新体系取代(见 第 7 章),但它是练习 less 时极好的 示例文件,所以如果你想边读本章边动手, 随时都可以键入上面那条命令。

* 脚注

尽管人们更倾向于使用 Terminfo 体系 (见第 7 章),但有些程序仍然在使用 Termcap,包括 less 本身。

在显示任何内容之前,less 会先清屏。 (你可以用 -X 选项禁止清屏。) less 启动时,会显示第一屏数据, 也就是你的显示器或窗口里放得下的 部分。在屏幕左下角你会看到一个提示符。 最初的提示符会告诉你正在显示的文件名。 根据你的系统如何配置,你可能还会看到 其他信息。例如:

/etc/termcap lines 1-33/18956 0%

在这个例子中,我们看的是 /etc/termcap 文件的第 1 到 33 行。 屏幕最上面一行是文件的第一行(0%)。 随后的提示符会更新行号和百分比。

在某些系统上,less 默认显示一个 简单得多的提示符,不带行号和百分比。 如果你的系统是这种情况,第一个提示符 只会显示文件名,例如:

/etc/termcap

随后的提示符会更简单;你只会 看到一个冒号:

:

在这类系统上,你可以用 -M 选项 让提示符显示更多信息(本章后面会讲)。

— 提示 —

对于有大量空闲时间的雄心勃勃的狂热 爱好者,less 提供了比历史上任何 其他分页器都更灵活的提示符定制功能。 (细节请看手册页。)

看到提示符之后,你就可以键入命令了。 我们一会儿就会讲各种命令,它们为数 众多。现在我只提一下最常用的那个命令: 按下 <Space> 键。这是告诉 less 显示下一屏数据。因此,只要不断 按 <Space>,你就可以从头到尾 一屏一屏地读完整个文件。

当你到达文件末尾时,less 会把 提示符变成:

(END)

如果你想退出,随时可以按 q。 不必等到文件末尾。所以,要看一个 文件,你要做的只是启动 less, 按 <Space> 直到看到想要看的 那么多内容,然后按 q 退出。

做个快速练习,试一下。键入下面 命令中的一条来显示 Termcap 文件:

less /etc/termcap
less -m /etc/termcap

你会看到第一屏数据。按几次 <Space>,一屏一屏地往下翻。 当你看腻了那份无穷无尽、含义晦涩、 早已过时的终端描述清单时,按 q 退出。

— 提示 —

当你用 less 显示文件时,在查看 文件的过程中有许多命令可以使用。 最重要的命令是 h(help,帮助)。 任何时候你都可以按 h 来显示 所有命令的摘要。

了解 less 的最好办法就是按 h,看看有哪些功能,然后动手 试验。

lessmore 的故事
(The Story of less and more)

正如我在本章前面解释的,最早的 Unix 分页器是 more(配合 BSD 使用) 和 pg(配合 System V 使用)。 你有时会听说,less 这个名字是 出于一句俏皮话:因为 lessmore 强大得多,所以玩笑就是 "less is more"。这听起来 有道理,但并不是真的。真正的 故事是这样的。

最早的 Unix 分页器 more 是个 简单的程序,用来一次一屏地显示数据。 名字叫 more,是因为每显示完一屏, 程序就会显示一个带 "More" 字样的提示符:

--More--

more 程序很有用,但它有严重的 局限。最重要的局限是:more 只能 从头到尾顺序显示数据,不能往回退。

1983 年,一位名叫 Mark Nudelman 的 程序员在一家叫 Integrated Office Systems 的公司工作。这家公司生产的 Unix 软件会 创建非常大的日志文件,里面存放着 交易信息和错误信息。有些文件大得让 当时版本的 vi 文本编辑器都读不 进去。因此,当 Nudelman 和其他程序员 想查找错误时,只好用 more 来 查看这些文件。

可问题在于:每当程序员在日志文件里 发现一条错误信息,都没法退回去看看是 什么导致了这个问题,也就是错误前面 紧接着的那几笔交易记录。程序员们常常 抱怨这一点。Nudelman 向我解释说:

"一群工程师站在实验室的一台终端前, 用 more 查看一个日志文件。我们发现 了一行表示错误的记录,像往常一样,我们 得先确定这行错误的行号。然后必须退出 more,重新启动它,再往前翻到错误 之前几行的位置,才能看到导致错误的 原因。有人抱怨这个过程太繁琐。另一个人 说:'我们需要一个能往后退的 more。'第三个人说:'是啊,我们 需要 LESS!'把大家都逗乐了。

Nudelman 琢磨着这个问题,忽然想到, 做一个能往回退的分页器并不难。1983 年 年末,他写出了这样一个程序,并且确实 把它命名为 less。起初,less 只在公司内部使用。不过在完善了这个 程序之后,Nudelman 觉得可以放心地把它 提供给外界,1985 年 5 月他这样做了。

Nudelman 把 less 作为开源软件 发布,这让许多其他人能够帮助他改进 这个程序。多年来,less 变得越来越 强大,也极受 Unix 用户欢迎,终于到了 取代 morepg(另一个 流行的 Unix 分页器)的程度。如今, less 是世界上使用最广泛的 Unix 分页器,并作为 GNU 工具集的一部分发行 (见第 2 章)。

一个有意思的细节:大多数程序的 版本号是 1.0、1.01、1.2、2.0 这样的 形式。Nudelman 用了一套更简单的办法。 从一开始,他就给 less 的每个新版本 一个自己的号码:1、2、3、4,依此类推。 所以,我写这本书时用的 less 是 394 版。

使用 less
(Using less)

当你用 less 阅读文件时,可以 使用的命令非常多。作为参考, Figure 21-2 汇总了其中最重要的 命令。想看更完整的摘要,你可以在 less 中按 h(帮助),也可以 在 Shell 提示符下键入下面的命令:

less --help

Figure 21-2
less: 最有用的
命令摘要
基本命令
h显示帮助信息
<Space>向前翻一屏
q退出程序
高级命令
g跳到第一行
G跳到最后一行
=显示当前行号和文件名
<Return>向前一行
n<Return>向前 n
b向后退一屏
y向后退一行
ny向后退 n
d向前(向下)半屏
u向后(向上)半屏
<Down>向前一行
<Up>向前一行
<PageUp>向后(向上)一屏
<PageDown>向前(向下)一屏
ng跳到第 n
np跳到文件的第 n% 处
/pattern向前搜索指定的模式
?pattern向后搜索指定的模式
n重复搜索:方向相同
N重复搜索:方向相反
!command执行指定的 Shell 命令
v用当前文件启动 vi 编辑器
-option更改指定的 option
_option显示 option 的当前值
 

当你显示那份完整的摘要时,会发现 命令多到你一辈子也用不完。例如, 向前(也就是向下)移动一行有五种 不同的方式,向后(向上)移动一行有五种 不同的方式,退出程序也有五种,如此 种种。别被吓倒。你只要掌握 Figure 21-2 里的命令就够了。

最好的策略是从我之前提到的三个 命令开始:<Space> 翻页, h 查看帮助,q 退出。等你用熟 了这三个,再自学 Figure 21-2 里 剩下的命令,一次学一个,直到把它们 全部记住。照着清单从上往下逐个练 就行。(我仔细挑选了这些命令并排好了 顺序,是的,你确实需要把它们全部记住。)

如果你需要一个用来练习的文件,就用 我在本章前面提到的 Termcap 文件。 下面这条命令可以帮你起步:

less -m /etc/termcap

(练习时用 -m 选项会有帮助,这样 提示符会显示你在文件中的位置。)

less 在文件中搜索
(Using less to Search Within a File)

Figure 21-2 中的大多数命令都很 直截了当。不过,我确实想 就搜索命令多说几句。当你想搜索某个 模式时,用 /(向前搜索)或 ?(向后搜索),后面跟上模式。这个 模式可以是简单的字符串,也可以是正则 表达式(第 20 章有介绍)。键入 /? 和模式之后,你需要按 <Return>,让 less 知道你 输入完毕。

下面看几个例子。要在文件中向前 搜索下一个出现的 "buffer",用:

/buffer

要向后搜索同样的模式,用:

?buffer

搜索是区分大小写的,所以如果你搜索 "Buffer",结果会不同:

/Buffer

如果你想使用不区分大小写的搜索,可以 用 -I 选项启动 less(本章 后面会讲),例如:

less -Im /etc/termcap

用这种方式启动 less 时,搜索 "buffer" 的结果与搜索 "Buffer" 或 "BUFFER" 相同。

如果你想在阅读文件的过程中开关 -I 选项,可以在 less 中使用 -I 命令。要显示这个选项的当前状态, 用 _I 命令。(在 less 内部更改 和显示选项,本章后面会讲。)

如果你想进行更复杂的搜索,可以使 用正则表达式。例如,假设你要搜索任何 包含 "buf"、后面跟着零个或多个小写字母 的字符串。你可以用:

/buf[:lower:]*
?buf[:lower:]*

(关于正则表达式的详细说明,包括大量 示例,请看第 20 章。)

一旦你输入了搜索命令,可以用 n(next,下一个)命令重复它。这会在 同一方向上执行完全相同的搜索。要在 相反方向上重复同一个搜索,用 N

每当你搜索一个模式,less 会在文件 中该模式出现的地方把它高亮显示。因此, 一旦你搜索了某个内容,翻页时就很容易 看到所有匹配之处。这种高亮会一直保留, 直到你输入下一次搜索。

— 提示 —

等你学会了 vi 编辑器,这些 less 命令会显得更有道理,因为其中 许多命令直接取自 vi。这是因为几乎所有经验丰富的 Unix 用户都熟悉 vi,所以让 less 使用同样的命令非常合理。

— 提示 —

如果你的空闲时间多得不善用掉,可以用 lesskey 命令更改 less 使用的 按键。细节请看 lesskey 的手册页。

原始模式与规范模式
(Raw and Cooked Mode)

在我们继续讨论之前,我想花点时间 谈几个重要的 I/O(输入/输出)概念, 它们能帮你更好地理解 less 及同类 程序是如何工作的。我们先从 一个定义说起。

设备驱动程序(DEVICE DRIVER),或者 更简单地叫驱动(DRIVER),是在操作系统 与某种特定类型的设备(通常是某种硬件) 之间提供接口的程序。当你使用 Unix 基于文本的命令行界面 (CLI,command line interface)时, 控制你终端的那个驱动称为 终端驱动程序(TERMINAL DRIVER)。

与某些其他驱动不同,终端驱动必须 提供一种交互式的用户界面,这就需要 对数据做特殊的预处理和后处理。为 满足这一需要,终端驱动使用所谓 行规程(LINE DISCIPLINE)。

Unix 有两种主要的行规程:规范模式 (CANONICAL MODE)和原始模式 (RAW MODE)。细节极其技术性,但基本 思想是:在规范模式下,你键入的字符会 累积在一个缓冲区(存储区域)里,直到 你按下 <Return> 键才会把内容 送给程序。在原始模式(也称为 非规范模式,NONCANONICAL MODE)下,你 一按键,每个字符就直接传给程序。阅读 Unix 文档时,你常会看到规范模式被 叫作 COOKED MODE(直译是"煮熟的"模式)。 (这当然是一个有趣的比喻,"cooked" (熟)与 "raw"(生)正好相反。)

程序员编写程序时,可以使用他想要 的任何一种行规程。原始模式让程序员 完全掌控用户的工作环境。例如 less 程序就在原始模式下工作,这使 它得以完全接管命令行和屏幕,按自己的 需要显示行、处理字符。

这就是为什么每当你按一个键, less 都能立刻响应;它不需要你按 <Return>。你只要按 <Space>,less 就显示更多 数据;你按 b,less 就在文件中 往后退;你按 q,程序就退出。 你会发现许多程序都在原始模式下工作, 比如文本编辑器 vi 和 Emacs。

在规范(熟)模式下,程序收到的是一行 一行完整的数据,而不是单个字符。这让 程序员不必在每个字符产生时就处理它。 这也让你可以在一行被处理之前修改 这一行。例如,你可以在按下 <Return> 之前用 <Backspace> 或 <Delete> 做更正。当你使用 Shell 时,你就是在规范模式下工作: 不按 <Return>,什么都不会 发送出去。

几乎所有交互式文本程序都使用 规范模式或原始模式。不过还有第三种 行规程,你也许会听说,尽管它如今 已经不太用了。

CBREAK MODE(半原始模式)是原始模式的 一个变体。大多数输入像原始模式一样 直接送给程序。但有几个非常重要的键 由终端驱动程序直接处理。这些键 (我们在第 7 章讨论过)就是发出那 五个特殊信号的键:intr(^C)、 quit(^\)、susp (^Z)、stop(^S)和 start(^Q)。所以说,半原始 模式大体上是"生"的,只带一点"火候"。 早年有人俏皮地称它为 "rare mode" (三分熟)。

配合 less 使用的选项
(Options to Use With less)

启动 less 时,有大量选项可以 使用,其中绝大多数都可以放心地 忽略(*)。就实际用途而言,你可以认为 less 的语法是这样的:

less [-cCEFmMsX ] [+command] [-xtab] [file...]

其中 command 是让 less 自动执行的 命令,tab 是你想使用的制表符间距, file 是文件的名字。

* 脚注

事实上,less 属于那种奇怪的命令, 就像 ls(见第 24 章)一样,它的 选项比字母表里的字母还多。很难说清 原因,但我怀疑跟甲状腺过于活跃有关。

最有用的三个选项是 -s-c-m-s(squeeze,压缩)选项 会把多个连续空行替换为一个空行。当输出 中多个空行并无特殊含义时,这能让内容 更紧凑。当然,原始文件不会有任何改动。

-c(clear,清除)选项告诉 less 从上往下显示每一屏新数据。 没有 -c 时,新的行会从屏幕 底部往上滚动。有些人觉得用 -c 读长文件更轻松。-C(大写 C)选项 与 -c 类似,只是在写入新数据之前 会清掉整个屏幕。这两个选项你都得 亲自试试,看自己喜欢哪个。

-m 这个名字来自 more,也就是 我前面提到的那个最初的 BSD 分页器。 more 的提示符会显示一个百分比, 告诉你已经翻到文件的什么位置。开发 less 时,给它设计了一个非常简单的 提示符:一个冒号。但为了满足那些用惯 more、想要更详细提示符的人,加进了 -m 选项。

-m 选项让提示符看起来像 more 的提示符,显示已经浏览了文件 的百分之多少。例如,假设你用 -m 显示 Termcap 文件(见本章前面的讨论):

less -m /etc/termcap

往下翻了一段之后,你会看到 提示符:

40%

这表示你已经看到文件的 40%。 (顺便说一句,你可以用 40p 命令 直接跳到这个位置。见 Figure 21-2。)

-M(大写 M)选项让提示符显示 更多信息:你会看到文件名和行号,以及 已经浏览的百分比。例如,如果你用:

less -M /etc/termcap

一个典型的提示符看起来是这样的:

/etc/termcap lines 7532-7572/18956 40%

这里的行号指的是正在显示的行范围, 本例中是第 7,532 到 7,572 行 (总共 18,956 行)。

我很喜欢的一个选项是 -E (end,结束)。它告诉 less 在显示到 文件末尾时自动退出。使用 -E 时, 你不必按 q 就能退出程序。当你明确 只想从头到尾读一遍文件、不打算回头看 时,这很方便。

-F(finish automatically,自动结束) 选项告诉 less:如果整个文件一屏就能 显示完,就自动退出。同样,这省得你 按 q 去退出程序。以我的经验, -F 用于很短的文件效果最好,而 -E 用于长文件效果最好。想看看 这是怎么回事,我们来创建一个很短的 文件 friends。先键入命令:

cat > friends

现在键入五六个朋友的名字,一行一个。 写完按 ^D 发送 eof 信号来 结束程序。(我们在第 7 章讨论过 ^D。)现在比较下面两条命令。注意 第二条命令你不必按 q 就能退出 程序。

less friends
less -F friends

+(加号)选项让你指定 less 从哪里开始显示数据。+ 后面的内容会作为初始命令被执行。例如, 要在文件末尾开始显示 Termcap 文件,用:

less +G /etc/termcap

要显示同一个文件,并从搜索单词 "buffer" 开始,用:

less +/buffer /etc/termcap

要从某一行开始,用 +g(go to, 跳转到),前面写上行号。例如,要从 第 37 行开始,用:

less +37g /etc/termcap

作为一种方便,less 允许你省略 g。因此下面两条命令都是从 第 37 行开始:

less +37g /etc/termcap
less +37 /etc/termcap

-I(ignore case,忽略大小写)选项 告诉 less 在搜索模式时忽略大小写的 差别。默认情况下,less 是区分大小写 的。例如,"the" 与 "The" 不同。但当你使用 -I 时,搜索 "the"、"The" 或 "THE" 的结果是一样的。

-N(number,编号)选项在你想在 输出中查看行号时很有用。使用这个选项时, less 会给每一行编号,很像 nl 命令(第 18 章)。例如, 下面两个例子产生的输出相似:

less -N file
nl file | less

当然,这两种情况下实际文件都没有被 改动。

使用 nl 与使用 less -N 有两个重要区别。第一, less 只按一种方式给行编号: 1、2、3 依此类推。而 nl 命令有 许多选项,让你在行号的生成方式上有很 大的灵活度:你可以选择起始号码、增量 等等(见第 18 章)。第二,less 给 所有行编号,连空行也不例外。默认情况下, nl 不给空行编号,除非你使用 -b a 选项。

最后,-x 选项后面跟一个数字, 告诉 less 把制表位设为指定的固定 间隔。这控制的是含制表符数据的间距。 例如,要以每 4 个空格一个制表位显示 一个名为 foo.c 的程序,用:

less -x4 foo.c

和大多数 Unix 程序一样,默认的制表 间隔是每 8 个空格(见第 18 章)。

如果你想在查看文件的过程中临时更改 某个选项,使用 -(连字符)命令, 后面跟上新的选项。它就像一个开/关 切换开关。例如,要在查看文件时打开 -M 选项(显示详细提示符),键入:

-M

要关闭该选项,只要再输入一次 同样的命令。

要显示某个选项的当前值,用一个 _(下划线)后面跟上选项。例如, 要查看提示符是怎么设置的,用:

_M

再看一个例子。你启动 less 时没有 用 -I 选项,而在查看文件的过程中 决定要做一次不区分大小写的搜索。你 只需要键入:

-I

输入你的搜索命令之后,再键入 -I 把这个选项关掉。这样做几次, 很容易就忘了当前状态。所以在任何时候, 你都可以通过键入下面的内容来检查 选项的状态:

_I

这是一个值得记住的实用套路。

— 提示 —

当你刚接触 less,想学会各个选项 的用法时,可以在显示文件的过程中用 -(更改选项)和 _(显示选项) 命令来实验。

如果你想学会用 -P 选项(我们没有 讨论过)来更改提示符,这尤其有用。你可以 改动提示符,并立刻看到结果。

何时用 less,
何时用 cat
(When to Use less
and When to Use cat)

正如我们在本章前面讨论的, lesscat 都可以用来显示 文件。用 less 时,文件是一屏一屏地 显示;用 cat 时,整个文件一次显示 出来。如果你预计文件比屏幕还长,最好 用 less。如果用 cat,大部分 内容会比你读的速度更快地滚出屏幕。 可是,如果文件很小呢?

如果你用 cat 显示一个很小的 文件 — 短到能放进屏幕 — 数据很快就显示出来,事情就结束了。 用 less 反而不方便,有两个原因。 第一,less 会清屏,把之前的输出 抹掉。第二,你得按 q 才能退出 程序,而当你只想快点看几行时,这很烦人。

当然,你可以用带 -F(自动结束) 选项的 less,它会在整个文件能一次性 显示完时自动退出程序。例如,假设 data 是一个很小的文件。你可以用 下面的命令快速显示它:

less -F data

实际上,你甚至可以指定 less 默认 就使用 -F 选项。(这通过设置 LESS 环境变量做到,本章后面会讲。) 一旦设好这个变量,你就不必再键入 -F,于是下面两条命令大致等价 (假设 data 是一个很小的文件):

less data
cat data

然而,如果你观察经验丰富的 Unix 用户,会发现他们显示短文件时总是用 cat,从不用 less。这是为什么?

有四个理由。第一,正如我提到的, less 会清屏,抹掉之前的输出, 这可能不方便。第二,键入 "cat" 比 键入 "less" 快。第三,名字 catless 可爱多了。最后,这样用 cat 是 Unix 用户区别于众人的一种 方式。

这些理由听起来可能无关紧要,但 Unix 用户喜欢自己的工作顺畅、快捷而有乐趣。 所以,如果你想看起来是个真正的 Unix 用户,而不是个什么都不懂的笨蛋,那么 文件很短时用 cat,其他情况用 less

用环境变量
定制你的分页器
(Using Environment Variables
to Customize Your Pager)

正如我们在第 15 章讨论的,Unix 哲学认为,每个工具都只该做一件事, 并且把它做好。因此,Unix 的分页器 (lessmorepg)都只 为提供一项服务而设计:一次一屏地 显示数据。如果另一个程序需要这种 功能,它不必自己实现这项服务,而是 使用一个分页器。

最常见的例子发生在你使用 man 程序(第 9 章)访问联机 Unix 手册时。 man 程序实际上并不显示页面的 正文,而是调用一个分页器,一屏一屏地 把页面显示给你看。

问题随之而来:man 和其他程序会 用哪个分页器?你可能会想,既然 less 是最流行的分页器,任何需要 这类工具的程序都会自动使用 less。 情况常常如此,但并不总是。例如在 某些系统上,man 程序默认会用 more 来显示手册页。这可能让人 恼火,因为如我们前面所讨论的, more 远不如 less 强大。

不过,你可以指定自己的默认 分页器。你要做的只是把一个名为 PAGER 的环境变量设为你想使用的 分页器的名字。例如,下面的命令把 less 设为你的默认分页器。第一条 命令适用于 Bourne Shell 家族(Bash、 Korn Shell);第二条命令适用于 C-Shell 家族(C-Shell、Tcsh)。

export PAGER=less
setenv PAGER less

要让这个改动永久生效,你只需把 相应的命令放进你的登录文件。 (环境变量在第 12 章讨论;登录文件在 第 14 章讨论。)

一旦你以这种方式设置了 PAGER 环境变量,所有需要外部分页器的程序 都会使用 less。即使 less 看起来已经是你的系统的首选分页器, 在登录文件里设置 PAGER 仍是个好 主意。这会明确地覆盖任何其他默认设置, 确保无论你的系统是怎么配置的, 都由你来说了算。

除了 PAGER,还有另一个环境变量 可以用来做进一步的定制。你可以把 变量 LESS 设为你每次启动程序时 都想使用的选项。例如,假设你总想用 -CFMs 这几个选项运行 less (本章前面有讨论)。下面的命令可以把 变量 LESS 设置妥当。(第一条 命令适用于 Bourne Shell 家族;第二条 命令适用于 C-Shell 家族。)

export LESS='-CFMs'
setenv LESS '-CFMs'

同样,这是应该放进登录文件的命令。 一旦放进去,less 就会总是以这几个 选项启动。无论是你自己运行 less, 还是 man 之类的其他程序替你运行 它,都是如此。

如果你哪天发现自己不得不用 more 程序(比如在一个没有 less 的系统上),你可以用同样的 方式,通过设置 MORE 环境变量来 指定自动使用的选项。例如,下面的命令 指定 more 总是以 -cs 选项 启动。(第一条命令适用于 Bourne Shell 家族;第二条适用于 C-Shell 家族。)

export MORE='-cs'
setenv MORE '-cs'

再说一次,你只需把相应的命令放进 登录文件,就能让你的偏好永久生效。

— 提示 —

less 程序实际上会查看 30 个 不同的环境变量,这带来了极大的灵活性。

最重要的变量是 LESS,也就是我们 讨论过的这一个。如果你好奇其他变量都 做什么,去看看 less 的手册页。

less 显示多个文件
(Displaying Multiple Files With less)

你用 less 对单个文件能做的 任何事,对多个文件同样能做。特别地, 你可以在文件之间来回切换,还可以 同时在多个文件中搜索模式。作为参考, Figure 21-3 汇总了相关命令。

Figure 21-3
less: 处理多个文件
可用的命令
:n切换到清单中的下一个文件
:p切换到清单中的上一个文件
:x切换到清单中的第一个文件
:e把一个新文件插入清单
:d从清单中删除当前文件
:f显示当前文件名(与 = 相同)
=显示当前文件名
/*pattern向前搜索指定的模式
?*pattern向后搜索指定的模式
 

要处理多个文件,你要做的只是 在命令行上指定两个以上的文件名。 例如,下面的命令告诉 less 你要 处理三个不同的文件:

less data example memo

在任何时刻,你只看到一个文件,我们 称之为当前文件(CURRENT FILE)。但 less 会维护一份所有文件的清单, 你随时可以从一个文件切换到另一个文件。 你还可以根据需要在清单中添加文件 或删除文件。

less 启动时,当前文件是清单中的 第一个。在上面这个例子里,当前文件是 data。要在清单中往前移动,你用 :n(next,下一个)命令。例如,如果你 正在读 data,键入 :n,就会切换 到 example,它成为新的当前文件。 如果再键入一次 :n,当前文件就 变成 memo

同样,你可以用 :p(previous, 上一个)命令在清单中往后退,还可以用 :x 命令跳到清单的开头。例如,如果 你正在读 memo 而键入 :p,当前 文件就变成 example。如果你改键 :x,当前文件就变成 data

要显示当前文件的名字,键入 :f。它是 = 命令的同义命令 (见 Figure 21-2)。此时,你不妨花点 时间练习这三个命令,然后再往下读。)

less 最强大的功能之一,是允许你 在多个文件中搜索一个模式。它的 工作原理是这样的。

正如我们在本章前面讨论的,你用 / 命令在文件内向前搜索,用 ? 向后搜索。用过这两个命令中的 任意一个之后,你可以键入 n 在同一 方向上继续搜索,或键入 N 在相反 方向上继续搜索。

例如,假设你键入命令:

/buffer

这会在当前文件内向前搜索字符串 "buffer"。一旦 less 找到了,你可以 键入 n 向前跳到下一个 "buffer" 出现之处,或者键入 N 向后跳到上一个 出现之处。

当你同时处理多个文件时,就多了一个 选择:你可以用 /*?*,而不 是用 /?。以这种方式搜索时, less 会把整个清单当作一个大文件来 对待。例如,假设你用上面那条命令启动 less:

less data example memo

当前文件是 data,文件清单是:

data example memo

你键入命令 :n,它在清单中把你 移到第二个文件 example。然后你键入 50p(50 百分比),把你移到 example 的中间。接着你键入下面这条 命令,向前搜索字符串 "buffer"。

/*buffer

这条命令从 example 中的当前位置 开始,向前搜索 "buffer"。搜索完成之后, 你可以按 n 向前重复这次搜索。如果你 反复按 n,less 本来会在文件末尾 停下来。但因为你用的是 /* 而不是 /,less 会自动移到清单中的下一个 文件(本例中是 memo)继续搜索。

同样,如果你反复按 N 向后搜索, 当 less 到达当前文件 (example)的开头时,它会自动移到上一个 文件(data)的末尾继续搜索。

当你用 ?* 代替 ? 做向后搜索时, 道理相同。这个 * 告诉 less: 在你使用 nN 命令时忽略 文件边界。

除了 :n:p:x/*?*,less 还有两条命令 帮你处理多个文件。这两条命令允许你 在清单中插入和删除文件。

要插入文件,你键入 :e(examine, 查看),后面跟上一个或多个文件名。新文件会 插入到紧跟当前文件的位置。其中第一个 新文件随即成为新的当前文件。例如,假设 文件清单是:

data example memo

当前文件恰好是 example。你键入 下面的命令,把三个文件插入清单:

:e a1 a2 a3

清单变成:

data example a1 a2 a3 memo

此时当前文件是 a1

要从清单中删除当前文件,你用 :d(delete,删除)命令。(当然, less 并不会删除真正的文件。)例如, 如果你在处理上面这份清单,键入了 :d,当前文件(a1)就从清单中 删掉了:

data example a2 a3 memo

上一个文件(example)成为新的 当前文件。

一开始,这些命令可能有点让人发懵, 尤其是没有办法把实际的清单显示出来 看看谁是谁。处理多个文件时,你得把 文件的次序记在脑子里。不过,当你要 显示或搜索多个文件时,你会发现这些 命令出奇地实用,所以值得学会。

more 显示文件
(Displaying a File Using more)

正如我们在本章前面讨论的,早期的 分页器 morepg 已被更强大的 程序 less 取代。虽然你可能永远见不到 pg,但你会偶尔碰到 more。例如, 你可能不得不使用一台没有 less 的 系统,只好用 more。或者你用的系统 默认分页器是 more,结果你可能在 不知不觉中用上了它。(*) 这种情况下, 了解一点这个程序对你有好处,所以本节 我替你把基础知识过一遍。

* 脚注

Solaris 系统就是这种情况。当你用 man 命令显示手册页时,默认分页器 是 more。如果你经常使用这样的系统, 可以通过设置 PAGER 环境变量把 less 变成你的默认分页器。见本章 前面的讨论。

more 的语法是:

more [-cs] [file...]

其中 file 是文件的名字。

more 程序一次一屏地显示数据。 每写完一屏,你会在屏幕左下角看到一个 提示符。提示符看起来是这样的:

--More--(40%)

(它叫 more 正是这个缘故。)

提示符末尾有一个括号里的数字。 它告诉你已经显示了多少数据。在我们的 例子里,提示符显示你已经看到文件的 40%。

使用 more 最简单的方式是指定 单个文件名。例如:

more filename

如果数据能放进一屏,就会一次显示 出来,more 自动退出。否则,数据会 一屏一屏地显示,提示符在底部。

看到提示符之后,你可以键入命令。 最常用的命令就是按 <Space> 键, 它显示下一屏数据。你可以反复按 <Space>,翻读整个文件。显示完最后 一屏数据后,more 会自动退出。

more 最常见的用途是显示管道的 输出,例如:

cat newnames oldnames | grep Harley | sort | more
ls -l | more

当你在管道中使用 more 时,提示符 不会显示百分比:

--More--

这是因为 more 是边收到数据边显示, 所以它并不知道总共有多少数据。

more 暂停时,有许多命令可以 使用。和 less 一样,more 工作在 原始模式下(本章前面有解释),所以你键入 单字符命令时不必按 <Return>。 不出所料,最重要的命令是 h (帮助),它会显示完整的命令摘要。

大体上,你可以把 more 看作功能 弱一些版本的 less。作为参考, more 最重要的命令汇总在 Figure 21-4 中。想看完整的摘要,请见 more 的手册页(man more)。

Figure 21-4
more: 有用的命令
基本命令
h显示帮助信息
<Space>向前翻一屏
q退出程序
高级命令
=显示当前行号
<Return>向前一行
d向前(向下)半屏
f向前翻一屏
b向后退一屏
/pattern向前搜索指定的模式
/重复上一次搜索
!command执行指定的 Shell 命令
v用当前文件启动 vi 编辑器
 

如我所说,按 <Space> 可以 向前翻一屏。你也可以按 d(down, 向下)向前半屏,或按 <Return> 向前一行。要向后翻一屏,按 b。 (注意:b 命令只在你读文件时起作用。 在管道里你没法后退,因为 more 不保存数据。)

要搜索模式,键入 /,跟上模式, 再按 <Return>。如果你愿意,可以 使用正则表达式(见第 20 章)。当 more 找到模式时,它会显示该位置 之前的两行,让你看到上下文。要重复 上一次搜索,输入不带模式的 /, 也就是 /<Return>。

启动 more 时,最有用的两个选项是 -s-c-s(squeeze, 压缩)选项把多个连续空行替换为一个空行。 当输出中多个空行并无特殊含义时,你可以用 这个选项让内容更紧凑。当然,这不会影响 原始文件。

-c(clear,清除)选项告诉 more 从上往下显示每一屏新数据。 每一行在被替换之前都会先清除。没有 -c 时,新的行从屏幕底部往上滚动。 有些人觉得用 -c 读长文件更轻松。 这得你自己试了才知道。

显示文件的开头:head
(Displaying the Beginning of a File: head)

在第 16 章,我们讨论了如何把 head 作为管道中的过滤器,从数据流 的开头挑选行。本节我演示如何单独使用 head,显示文件的开头部分。当你这样 使用 head 时,语法是:

head [-n lines] [file...]

其中 lines 是你想显示的行数, file 是文件的名字。默认情况下, head 显示文件的前 10 行。当你想快速 看一眼文件、检查它的内容时,这很有用。 例如,要显示一个名为 information 的文件的前 10 行,用:

head information

如果你想显示不同的行数,用 -n 选项指定那个数字。例如,要显示 同一个文件的前 20 行,用:

head -n 20 information

— 提示 —

最初,headtail(下一节 讨论)并不要求你使用 -n 选项;你 可以直接键入一个连字符后面跟上数字。 例如,下面这些命令都显示 15 行输出:

calculate | head -n 15
calculate | head -15

calculate | tail -n 15
calculate | tail -15

按正式规定,现代版本的 headtail 应当要求使用 -n 选项, 这就是我把它写出来的原因。不过,大多数 Unix 和 Linux 版本两种语法都接受,而且 如果你观察经验丰富的 Unix 用户,会发现 他们常常省略 -n

显示文件的结尾:tail
(Displaying the End of a File: tail)

要显示文件的结尾,你用 tail 命令。语法是:

tail [-n [+]lines] [file...]

其中 lines 是你想显示的行数, file 是文件的名字。

默认情况下,tail 显示文件的最后 10 行。例如,要显示名为 information 的文件的最后 10 行,用:

tail information

要显示不同的行数,用 -n 选项, 后面跟上数字。例如,要显示 information 文件的最后 20 行,用:

tail -n 20 information

严格说来,你必须键入 -n 选项。 不过,正如我在上一节解释的,通常你 省略它也行。你可以直接键入一个 -(连字符),后面跟上你想显示的 行数。因此,下面两行是等价的:

tail -n 20 information
tail -20 information

如果你在数字前面加一个 + (加号),tail 会从那个行号显示到 文件末尾。例如,要从第 35 行显示到文件末尾,用:

tail -n +35 information.

这种情况下,不要省略 -n,以确保 tail 不会把这个数字当成文件名。

监视不断增长的文件尾部:tail -f
(Watching the End of a Growing File: tail -f)

tail 命令有一个特殊的选项, 让你能看着一个文件一行一行地长大。 当你必须等待数据被写入文件时,这个 选项非常有用。例如,你可能想监视一个 一次往文件末尾写一行的程序。或者, 如果你是系统管理员,你可能想盯着一份 日志文件,重要的消息会不时写进去。

要以这种方式运行 tail,你使用 -f 选项。语法是:

tail -f [-n [+]lines] [file...]

其中 lines 是你想显示的行数, file 是文件的名字。(关于 lines 参数的说明见上一节。)

-f 选项告诉 tail:到达文件 末尾时不要停止。相反,tail 会无限期 地等待,并随着文件变大显示更多输出。 (这个 -f 代表 "follow",即跟随。)

例如,假设接下来几分钟里,某个程序会 不断把输出添加到名为 results 的文件 末尾。你想跟踪这个程序的进展。键入:

tail -f results

你一敲这条命令,tail 就会显示 文件的最后 10 行。然后程序开始等待, 监视文件中的新数据。每当有新行加入, tail 就自动显示给你看。

当你使用 tail -f 时,它会无限期等待新的 输入;程序不会自行停止。要让它停下, 你必须按 ^C(intr 键;见 第 7 章)。这会带来一个小麻烦:在你 停掉 tail 之前,你没法再键入其他 命令。有两种办法处理这个局面。

第一,你可以把 tail -f 放到后台运行(见 第 26 章),方法是在命令末尾加一个 &(and 符号)。

tail -f results &

tail 放到后台运行,它可以无人 看管地一直跑下去,不会占住你的终端。 而且,因为 tail 就在你工作的同一个 窗口或虚拟控制台里运行,任何新输出你 都会立刻看到。缺点是 tail 的输出会 和你运行的其他程序的输出混在一起, 可能让人糊涂。

注意:当你在后台运行一个程序时,你 没法用 ^C 停掉它。你必须改用 kill 命令。(细节在第 26 章 解释。)

使用 tail -f 的另一种办法,是让它 在自己单独的终端窗口或虚拟控制台里运行 (见第 6 章)。如果这样做,一旦 tail 开始运行,你就可以不理它,在 第二个窗口或虚拟控制台里干你的活,想 什么时候回看 tail 就什么时候回看。 以这种方式用两个窗口或控制台,不仅能 让你在 tail 运行时执行其他命令,还能 把 tail 的输出分隔开来。唯一的缺点 是,你必须记得留意 tail 所在的那个 窗口或控制台。

如果你想练习 tail -f,可以试试这个小实验。 先打开两个终端窗口(见第 6 章)。 在第一个窗口里,用 cat 命令创建一个 名为 example 的小文件:

cat > example

键入 4-5 行,然后按 ^D 结束输入并 停止该命令。(用 cat 创建小文件 在第 16 章解释;用 ^D,也就是 eof 键,在第 7 章解释。)

在第二个终端窗口里,键入下面的 tail 命令:

tail -f example

tail 程序会列出 example 的最后 10 行,然后等待新的 输入。现在回到第一个窗口,往文件里再 添加几行。最简单的办法是用 >> 追加数据(见第 16 章)。键入 命令:

cat >> example

现在你想键入多少行就键入多少行, 每行末尾按 <Return>。注意,你在 第一个窗口里每键入一行,这一行就会 作为 tail 的输出出现在第二个 窗口里。

实验做完后,在第一个窗口里按 ^D 告诉 cat 没有更多输入了。 然后在第二个窗口里按 ^C 停掉 tail

回头看一看不久前给出的 tail -f 用法,你会发现你可以指定 不止一个文件名。这是因为 tail 能同时 监视多个文件,并在其中任何一个文件收到 新数据时提醒你。如果你想动手试试,照 上一个例子那样准备好两个终端窗口。在 第一个窗口里,像前面说的那样用 cat 创建两个小文件:

cat > file1
cat > file2

在第二个窗口里,运行下面的命令:

tail -f file1 file2

现在回到第一个窗口,依次用下面的命令 往两个文件中的某一个添加行:

cat >> file1
cat >> file2

注意,每当你(在第一个窗口里)用 cat 往其中一个文件添加行时, tail(在第二个窗口里)都会显示出 文件名,后面跟着新加入的行。

二进制、八进制与十六进制
(Binary, Octal and Hexadecimal)

作为本章的结尾,我们要谈谈 odhexdump 这两个命令, 它们用来显示二进制文件中的数据。 要解读这些命令的输出,你需要理解二进制、 八进制和十六进制计数系统。所以,在继续 之前,先花一点时间讨论这些非常重要的概念。

尽管这三种计数系统对计算机科学和计算机 编程非常重要,但不幸的是,详细的讨论 超出了本书的范围。在本节中,我只介绍 基本思想,帮你入门。如果你下定决心要 成为一位计算机专业人士,我的建议是自己 花些时间,把这些主题好好深入研究一番。

大多数时候,我们使用的数字由 0 到 9 这 10 个数字组成。因此,我们日常的数字是 由 10 的幂构成的:1、10、100、1000, 依此类推。我们把这样的数字称为十进制数 (DECIMAL NUMBERS)。例如,十进制数 19,563 实际上是:

(1x10,000) + (9x1,000) + (5x100) + (6x10) + (3x1)

或者,用指数表示:

(1x104) + (9x103) + (5x102) + (6x101) + (3x100)

我们把这个系统称为十进制(BASE 10)或 十进制系统(DECIMAL SYSTEM)。这个名字 来自于所有数字都由 10 个不同的数字位 构成的想法。在计算机世界里,实际上还有 三种基数比十进制更重要:

• 基数 2(二进制):使用 2 个数字位,0-1
• 基数 8(八进制):使用 8 个数字位,0-7
• 基数 16(十六进制):使用 16 个数字位,0-9 A-F

这些系统的重要性源于计算机数据的存储方式。 这是因为所有数据都被组织成一串串电迹, 从概念上讲,它们要么被视为"关",要么 被视为"开"。对任何类型的数据都是如此, 无论它存在于计算机内存中(如 RAM 或 ROM), 还是存储在磁盘、CD、DVD、闪存或其他设备上。

作为一种速记符号,我们用数字 0 表示"关", 用数字 1 表示"开"。这样一来,任何数据项, 无论长短,都可以看作 0 和 1 的图案。事实上, 从技术意义上讲,计算机科学家正是这样看待 数据的:把它们看成一条条由 0 和 1 组成的长流。

这里有几个简单的例子。在 ASCII 码中, 字母 "m" 用下面的图案表示:

01101101

单词 "mellow" 表示为:

011011010110010101101100011011000110111101110111

(关于 ASCII 码的讨论,见第 19 章和第 20 章。 要查看该码的详细内容表,见附录 D。)

ASCII 码只用来表示单个字符。当涉及到处理 数值时,我们会使用各种不同的系统。不深入 细节,下面是数字 3.14159 用一个叫作 "单精度浮点"的系统表示的样子:

01000000010010010000111111010000

如果这些例子看起来有点令人困惑,别担心。 细节非常复杂,现在也不重要。重要的是, 你应该明白,对计算机科学家来说,所有数据 — 无论什么类型、多少 — 都存储为由 0 和 1 组成的长序列。因此,学会处理只由 0 和 1 组成的数字很重要,我们称之为二进制数 (BINARY NUMBERS)。

在计算机科学中,作为数据存储的单个 0 或 1 称为一个位(BIT,"binary digit" 的缩写); 排成一行的 8 个位称为一个字节(BYTE)。 例如,前面那个例子包含一个由 32 位组成的 二进制数,也就是 4 字节的数据。我们把这个 系统称为二进制(BASE 2)或二进制系统 (BINARY SYSTEM)。这个名字提醒我们,在 基数 2 中,所有数字只由两个不同的数字位 构成:0 和 1。

如果你是初学者,基数 2 的优势不会显而易见, 二进制数看起来会毫无意义。然而,一旦你有了 一些经验,你就会看到二进制数直接反映了数据 存储方式的底层现实。这就是为什么在许多情况下, 使用二进制数比使用十进制数有明显优势。 不过,有一个问题:二进制数很难使用,因为它们 占用大量空间,而且笨拙、看着让人糊涂。

作为一种折中,有两种方法可以把二进制数表示得 更紧凑,同时又不失去与底层数据的直接联系。 它们叫作"基数 8"和"基数 16"。在我解释它们 如何工作之前,我想先岔开一句,给你看看在 基数 2 中怎么计数。

在十进制中,我们从 0 开始计数,直到用完所有 数字位。然后我们给左边的数字位加 1,再从 0 重新开始。例如,我们从 0 开始,数 1、2、3、4、 5、6、7、8、9,这时我们就用完了数字位。 下一个数是 10。我们继续 11、12、13、14、15、 16、17、18、19,然后到 20。依此类推。

基数 2(实际上,所有基数)的工作方式相同。 唯一的区别在于我们能用完数字位之前可以数到 多远。在基数 2 中,我们只有两个数字位:0 和 1。 我们从 0 开始计数,然后到 1,这时就用完了 数字位。所以下一个数是 10。然后是 11、100、 101、110、111、1000,依此类推。换句话说:

0 (base 10) = 0 (base 2)
1 (base 10) = 1 (base 2)
2 (base 10) = 10 (base 2)
3 (base 10) = 11 (base 2)
4 (base 10) = 100 (base 2)
5 (base 10) = 101 (base 2),依此类推。

看看图 21-5,你会看到十进制数 0 到 20, 以及它们对应的基数 2 表示。(目前你可以忽略 另外两列。)

图 21-5:十进制、二进制、八进制与十六进制的对应关系
(Figure 21-5: Decimal, Binary, Octal and Hexadecimal Equivalents)

在日常生活中,我们使用十进制(基数 10)数。 对计算机而言,数据以一种用二进制(基数 2)数 最能体现的形式存储。用八进制(基数 8)或 十六进制(基数 16)数可以把这些数据写成更紧凑的 形式。(详见正文。)

本表展示了十进制数 0 到 20 用二进制、八进制 和十六进制表示的对应方式。你能看出规律吗? 它们对你来说有意义吗?

十进制
(Base 10)
二进制
(Base 2)
八进制
(Base 8)
十六进制
(Base 16)
0000
1111
21022
31133
410044
510155
611066
711177
81000108
91001119
10101012A
11101113B
12110014C
13110115D
14111016E
15111117F
16100002010
17100012111
18100102212
19100112313
20101002414

基数 8 也称为八进制(OCTAL)。用这个基数, 我们有 8 个数字位,0 到 7,所以我们这样数: 0、1、2、3、4、5、6、7、10、11、12,依此类推。

基数 16,也称为十六进制(HEXADECIMAL)或 HEX,用 16 个数字位以类似的方式工作。当然, 如果只用常规数字位,我们只有 10 个:0 到 9。 要在基数 16 中计数,我们还需要 6 个数字位, 于是我们用符号 A、B、C、D、E 和 F。因此, 在基数 16 中,我们这样数:0、1、2、3、4、5、 6、7、8、9、A、B、C、D、E、F、10、11、12, 依此类推。

再看一眼图 21-5。到目前为止,四列都应该 开始说得通了。当然,这些对你可能都是全新的, 我并不指望你立刻就能习惯三种新的计数方式。 然而,总有一天这一切都会显得容易。例如, 一位经验丰富的程序员看到二进制数 "1101" 能立刻想到:十进制的 13。或者他看到基数 8 的数 "20" 能立刻想到:十进制的 16。或者他 看到十进制的数 "13" 能立刻想到:十六进制的 D。 总有一天对你来说也一样容易;一旦你练习过, 其实并不那么难。(※)

* 脚注

我在滑铁卢大学(加拿大)读本科时,为学校 计算中心做系统程序员。那是 IBM 大型机的 年代,对系统程序员来说,熟练掌握十六进制 运算尤为重要。我们大多数人都能做基数 16 的 加法,少数几个人能做减法。当然,更复杂的 计算我们用计算器。然而,我的主管是个了不起的 家伙。他叫 Romney White,他居然能做基数 16 的 乘法。Romney 是我见过的唯一能做到这一点的人。

顺便说一句,如今 Romney 在 IBM 工作,他是 在大型机上使用 Linux 的专家。你有空的时候, 在网上查查他。(搜索:"romney white" + "linux"。)

那么,为什么这一切如此重要?答案就在 图 21-6 中。假设你问这样一个问题:有多少个 不同的 3 位二进制数?答案是 8 个,从 000 到 111(把前导零也算进去)。在图 21-6 中,你可以 看到这 8 个值分别对应一个特定的八进制数。 例如,000(二进制)等于 0(八进制);101 (二进制)等于 5(八进制);依此类推。这意味着 任意 3 个位(二进制数字位)的图案都可以用 单个八进制数字位来表示。反过来,任意八进制 数字位都对应一个特定的 3 位图案。

图 21-6:八进制与二进制的对应关系
(Figure 21-6: Octal and Binary Equivalents)

每三个基数 2 数字位(位)的组合都可以用单个 八进制数字位表示。同样,每个八进制数字位都 对应一个特定的 3 位图案。

八进制
(Base 8)
二进制
(Base 2)
0000
1001
2010
3011
4100
5101
6110
7111

这是一个极其重要的概念,所以我们花一点时间 确保你理解它。举个例子,考虑我们之前看到的 "mellow" 的二进制表示:

011011010110010101101100011011000110111101110111

这里有 48 个位。我们把它们分成每三个一组:

011 011 010 110 010 101 101 100
011 011 000 110 111 101 110 111

用图 21-6 中的表,我们可以把每 3 个位替换成 对应的八进制数。也就是说,我们把 011 换成 3, 010 换成 2,依此类推:

3 3 2 6 2 5 5 4 3 3 0 6 7 5 6 7

去掉空格,我们得到:

3326255433067567

注意八进制比二进制紧凑得多。然而,因为每个 八进制数字位正好对应 3 个位(二进制数字位), 我们完整地保留了所有信息。为什么能这么巧妙地 对应呢?这是因为 8 是 3 的精确幂。具体来说, 8 = 23。因此,基数 8 中的每一位对应 基数 2 中的三位。

到了这里,你可能会想,如果我们用一个基于 2 的 更高次幂的计数系统,能不能把长长的位串表示得 更紧凑?答案是能。24 的值是 16,用基数 16(十六进制)我们能做得比基数 8 更好。这是 因为每个十六进制数字位可以表示 4 个位。你可以 在图 21-7 中看到这一点。

图 21-7:十六进制与二进制的对应关系
(Figure 21-7: Hexadecimal and Binary Equivalents)

每四个基数 2 数字位(位)的组合都可以用单个 十六进制数字位表示。同样,每个十六进制数字位 都对应一个特定的 4 位图案。

十六进制
(Base 16)
二进制
(Base 2)
00000
10001
20010
30011
40100
50101
60110
70111
81000
91001
A1010
B1011
C1100
D1101
E1110
F1111

要看这是如何工作的,再一次考虑 "mellow" 的 48 位二进制表示:

011011010110010101101100011011000110111101110111

首先,把位分成每四个一组:

0110 1101 0110 0101 0110 1100 0110 1100 0110 1111 0111 0111

用图 21-7 中的表,我们把每 4 个位替换成 对应的十六进制数:

6 D 6 5 6 C 6 C 6 F 7 7

去掉空格,我们得到:

6D656C6C6F77

因此,下面三个值都是等价的。第一个是二进制 (基数 2),第二个是八进制(基数 8),第三个是 十六进制(基数 16):

011011010110010101101100011011000110111101110111
3326255433067567
6D656C6C6F77

这一切意味着什么?因为我们可以用八进制字符 (每个字符 3 位)或十六进制字符(每个字符 4 位) 来表示二进制数据,所以我们可以通过把数据表示为 一长串八进制或十六进制数来显示任何二进制文件的 原始内容。事实上,这正是在我们讨论 hexdumpod 命令时我要教你的事情。不过在此之前, 我们还需要再讨论一个主题。

读写二进制、八进制与十六进制
(Reading and Writing Binary, Octal and Hexadecimal)

当你看到数字 101 时,你会想到什么?大多数人会 说"一百零一"。然而,作为一名计算机专业人士, 你可能会想:我怎么知道我看的是一个十进制数呢? 也许 "101" 指的是一个基数 2(二进制)数,那样 它的十进制值就是 5:

(1x22) + (0x21) + (1x20) = 4 + 0 + 1 = 5

或者它是基数 8(八进制),这样它的值是十进制的 65:

(1x82) + (0x81) + (1x80) = 64 + 0 + 1 = 65

又或者它可能是基数 16(十六进制),值为十进制的 257?

(1x162) + (0x161) + (1x160) = 256 + 0 + 1 = 257

你可以看到问题所在。而且,如果你要说数字 101, 你该怎么读?如果你知道它是十进制,你会用常规的 方式念它。但如果它是二进制、八进制或十六进制呢? 叫它"一百零一"就没有意义了。

在数学中,我们用下标来表示基数。例如, 10116 表示"基数 16 的 101"; 1018 表示"基数 8 的 101"; 1012 表示"基数 2 的 101"。当你看不到 下标时,你就知道你看的是一个十进制数。

在计算机上,我们没有下标。取而代之,最常见的约定 是使用一个前缀,由数字 0 后跟一个字母来表示基数。 前缀 0x 表示"基数 16"(十六进制);0o 表示 "基数 8"(八进制);0b 表示"基数 2"(二进制)。 例如,你可能会看到 0x101、0o101 或 0b101。有时, 我们用另一种方式表示八进制,即用一个(本来不必要的) 前导 0,比如 0101。你可以在图 21-8 中看到这些 约定的示意。

图 21-8:表示十六进制、八进制与二进制数的约定
(Figure 21-8: Conventions for Indicating Hexadecimal, Octal, and Binary Numbers)

当我们使用非十进制计数系统时,我们需要书写和口头的 约定来表示一个数的基数。在数学中,我们写这类数时用 下标。在计算机上我们没有下标,所以通常用一个特殊的 前缀。当我们谈论非十进制数时,我们逐个念出每个数字位。

含义 数学 计算机 读法
基数 10 的 101101101"一百零一"
基数 16 的 101101160x101"十六进制的一-零-一"
基数 8 的 10110180101 或 0o101"八进制的一-零-一"
基数 2 的 10110120b101"二进制的一-零-一"

我明白,起初这一切可能有点令人困惑。不过大多数 时候,所用的数的类型从上下文中是清楚的。事实上, 你常常光看一个数就能猜出它的基数。例如,如果你 看到 110101011010,那很可能是个二进制数。如果你 看到像 45A6FC0 这样的数,你知道那是个十六进制数, 因为只有十六进制使用数字位 A-F。

对于十六进制数,你会看到大写和小写字母都被用作 数字位。例如,两个数 0x45A6FC0、0x45a6fc0 是相同的。 不过大多数人更喜欢用大写数字位,因为更容易读。

当我们谈论数字时,规则很简单。当我们提到十进制数时, 我们用通常的方式念。例如,基数 10 的 101 念作 "一百零一";3,056 念作"三千零五十六"。

对于其他基数,我们只是逐个念出数字位的名字。 有时我们会提及基数。例如,如果我们想说基数 16 的 101,我们说"基数 16 的一-零-一"或"十六进制的一-零-一"; 基数 16 的 3,056 是"基数 16 的三-零-五-六"或 "十六进制的三-零-五-六"。

同样,基数 8 的 101 是"基数 8 的一-零-一"或"八进制的 一-零-一";基数 2 的 101 是"基数 2 的一-零-一"、 "二进制的一-零-一",或类似的说法。你可以在 图 21-8 中看到这些读法的示意。

为什么我们用十六进制而不是八进制
(Why We Use Hexadecimal Rather Than Octal)

在第 7 章,我们谈到了过去用来制造计算机内存的 技术。我特别提到,在 20 世纪 50 年代直至 60 年代, 内存是由微小的磁芯制成的。因此,单词 CORE(磁芯) 成了内存(memory)的同义词。

在那个年代,调试程序很困难,尤其是当程序意外中止时。 为了帮助解决这类问题,程序员可以指示操作系统在程序 中止的那一刻打印该程序所用内存的内容。然后程序员可以 研究打印出的数据,试着弄清楚发生了什么。正如我在 第 7 章所解释的,这样的数据被称为磁芯转储 (CORE DUMP),解读它需要很高的技巧。如今,"core dump" 这个说法仍在使用,但你有时也会看到内存转储 (MEMORY DUMP)或转储(DUMP)这两个词来代替它。

20 世纪 70 年代初 Unix 诞生时,调试可能非常困难, 程序员常常不得不保存并检查转储。尽管技术已经演进 — 磁芯已被半导体取代 — 内存仍被称为磁芯,内存 内容的副本仍被称为磁芯转储。因此,当 Unix 把内存内容 保存到文件中以供稍后检查时,这个文件被称为核心文件 (CORE FILE),这种文件的默认名字是 core

因此,从一开始,Unix 程序员就需要检查核心文件。为了 满足这个需求,Unix 的开发者创建了一个程序,把核心文件 的内容以八进制(基数 8)数字位显示出来。这个程序取名 为 od("octal dump",八进制转储),多年来它已被 证明是一个格外有用的程序。即使在今天,使用 od 仍然是我们查看二进制数据的最佳方式之一。

正如我们在上一节所讨论的,二进制数据既可以表示为 八进制数(每个数字位 3 位),也可以表示为十六进制数 (每个数字位 4 位)。八进制相对容易学,因为它使用 我们已经熟悉的数字位(0 到 7)。而十六进制则需要 16 个数字位,其中 6 个(A、B、C、D、E、F)不属于我们的 日常文化。因此,十六进制比八进制难学得多。尽管如此, 十六进制的使用远比八进制广泛。这有三个原因。

第一,十六进制比八进制紧凑得多。(准确地说,十六进制 比八进制紧凑 4/3 倍。)如果你想显示位,用十六进制字符比用八进制字符 要少得多就能完成任务。

第二个原因是十六进制更流行,这与位的使用方式有关。 计算机处理器把位组织成称为字(WORDS)的基本单位, 字的大小取决于处理器的设计。自 20 世纪 60 年代中期 以来,大多数处理器使用 16 位或 32 位的字;如今, 使用 64 位字的处理器很常见。然而在 20 世纪 50 和 60 年代,许多计算机,尤其是科学计算机,使用 24 位或 36 位的字。

对于 24 位或 36 位的字,八进制或十六进制都可以用, 因为 24 和 36 都能被 3 和 4 整除。由于八进制更简单, 它在 20 世纪 50 和 60 年代被广泛使用。

对于 16 位、32 位或 64 位的字,八进制很难用,因为 16、 32 和 64 都不能被 3 整除。然而,十六进制可以用,因为 16、32 和 64 都能被 4 整除。因此,自 20 世纪 70 年代 以来,十六进制用得越来越多,八进制用得越来越少。

十六进制被如此广泛使用的第三个原因是:虽然它比八进制 难学,但一旦你学会了,它就很便于使用。因此, 即便是最早的 od 版本也带有一个以十六进制显示 数据的选项。

正如我早先提到的,久经考验的 od 程序已经存在 多年了,事实上从 Unix 诞生之初就有。然而,1992 年, 另一个这样的程序 hexdump 为 BSD(Berkeley Unix; 见第 2 章)而写。如今,hexdump 随处可得, 不仅在 BSD 系统(如 FreeBSD)上,也作为许多 Linux 发行版的一部分。

大多数有经验的 Unix 人士往往会在 odhexdump 之间挑一个自己偏爱的来用。因此,我要 把两者都教给你,好让你看看自己最喜欢哪一个。

显示二进制文件:hexdumpod
(Displaying Binary Files: hexdump, od)

hexdumpod(八进制转储)最初的用途是 查看核心文件中包含的内存转储。通过检查转储,程序员 可以追踪到那些本来难以捉摸的 bug。如今,有了好得 多的调试工具,程序员很少手工查看核心文件了。不过, hexdumpod 仍然很有用,因为它们能以 可读的格式显示任何类型的二进制数据。事实上,这两个 程序是用于查看二进制文件内部的主要基于文本的工具。

由于这两个程序都能胜任这项工作,我会把两者都教给你。 然后你可以动手实验,看看自己更喜欢哪一个。这两个程序 之间最大的区别在于:hexdump 默认以十六进制显示 数据,而更老的 od 默认用八进制。因此,如果你用 od,你就得记住那些能生成十六进制输出的具体选项。

另一个需要考虑的因素是,od 在所有 Unix 系统上都 有,而 hexdump 则不然。例如,如果你用 Solaris, 你可能没有 hexdump。因此,如果你处理二进制文件 并且更喜欢 hexdump,你仍然应该了解一点 od, 以防有一天你不得不用它。

在进入语法之前,先看看一些典型的输出。在 图 21-9 中,你看到的是存放 ls 程序的文件里 一部分二进制数据。(ls 程序用来列出文件名。我们会 在第 24 章讨论它。)

图 21-9:以十六进制和 ASCII 显示的二进制数据样例
(Figure 21-9: Sample binary data displayed as hexadecimal and ASCII)

你可以用 hexdumpod 命令来显示二进制数据。 这里是一段以规范格式显示的数据样本;也就是说,左边是 十六进制的偏移量,中间是十六进制的数据,右边是同样的 数据以 ASCII 字符显示。这个特别的例子取自包含 GNU/Linux ls 程序的文件。

 偏移量 十六进制 ASCII
 000120  00 00 00 00 00 00 00 00  00 00 00 00 06 00 00 00  |................|
 000130  04 00 00 00 2f 6c 69 62  2f 6c 64 2d 6c 69 6e 75  |..../lib/ld-linu|
 000140  78 2e 73 6f 2e 32 00 00  04 00 00 00 10 00 00 00  |x.so.2..........|
 000150  01 00 00 00 47 4e 55 00  00 00 00 00 02 00 00 00  |....GNU.........|
 000160  06 00 00 00 09 00 00 00  61 00 00 00 76 00 00 00  |........a...v...|
 000170  00 00 00 00 4c 00 00 00  4b 00 00 00 3f 00 00 00  |....L...K...?...|
 

当你检查文件内的数据时,有时你需要知道你所看内容的 确切位置。当你用 less 查看文本文件时,弄清楚自己 在哪里很容易。任何时候,你都可以用 =(等号)命令 显示当前行号。或者,你可以用 -M 选项在提示符中 显示当前行号,也可以用 -N 选项在每一行左边显示 一个行号。

对于二进制文件,没有行,所以行号没有意义。取而代之, 我们用偏移量(OFFSET)标记文件内的每个位置,它是一个 告诉你距离文件开头有多少字节的数字。第一个字节的偏移量 是 0;第二个字节的偏移量是 1;依此类推。

看看图 21-9 中的样本数据。偏移量 — 它不是数据的 一部分 — 在最左边一列。在我们的例子中,所有数字都是 十六进制的,所以第一个数据字节的偏移量是 0x120(即十六进制 的 120),也就是十进制的 288。因此,我们例子中的第一个数据 字节是文件里的第 289 个字节。(记住,偏移量从 0 开始。)

输出的第一行包含 16 个字节。因此,偏移量从 0x120 到 0x12F。第二行从偏移量 0x130 开始。在每个偏移量的右边, 每行 16 个字节以两种不同格式显示。中间一列是十六进制的 数字位,按字节分组。(记住,1 字节 = 8 位 = 2 个十六进制 数字位。)右边,同样的数据以 ASCII 字符显示。

在大多数二进制文件中,你会注意到有些字节包含真正的 ASCII 字符。看最右边一列,就很容易识别这些字节。在我们 的例子中,你可以看到字符串 /lib/ld-linux.so.2GNU。按照约定,那些不对应可打印 ASCII 字符的字节 用一个 .(句点)字符表示。你可以在我们的例子中看到 许多这样的字节。

二进制文件中的大多数字节不是字符;它们是机器指令、 数值数据等等。你可以从最右边一列看出这一点,那里你 会看到大部分是 . 标记,夹杂着零星的随机字符。在 我们的例子中,第一行和最后两行都是非 ASCII 数据。少数 字节确实包含碰巧对应于字符的值,但这是巧合,没有意义。

图 21-9 中显示数据的方式称为规范格式(CANONICAL FORMAT)。 这种用于显示或打印二进制数据的格式每行包含 16 个字节。 每行的左边是十六进制的偏移量。中间是实际的字节,也是 十六进制的。右边是对应的 ASCII。

hexdumpod 都能以许多不同的格式显示 二进制。事实上,两个命令都支持大量选项,让你能极大地 控制数据的显示方式。不过大多数时候,最好使用规范格式。 因此,在我们对这两个命令的讨论中,我会告诉你用哪些选项 来产生这种输出。如果你需要了解其他变体的信息,可以在各自 的 man 手册页上找到。

我们从 hexdump 开始,因为它用起来更简单。要用 hexdump 以规范格式显示一个二进制文件,语法很简单:

hexdump -C [file...]

其中 file 是一个文件的名字。

hexdump 程序有很多选项让你控制输出。不过,有一个 重要的捷径:如果你用 -C(canonical,规范)选项, hexdump 会自动使用合适的选项组合,从而产生规范输出。

这里举个例子。假设你想查看包含 ls 程序的那个二进制 文件的内部。首先,你用 whereis 程序找到该文件的路径名 — 也就是确切位置。(我们将在第 24 章讨论路径名和 whereis,所以现在不用担心细节。)要用的命令是:

whereis ls

典型的输出是:

ls: /bin/ls /usr/share/man/man1/ls.1.gz

输出显示了程序和它的 man 手册页的确切位置。我们只对 程序感兴趣,所以我们用第一个路径名:

hexdump -C /bin/ls | less

这条命令以规范格式显示整个文件的内容。就这么简单。

如果你想限制显示的数据量,还有两个选项可以用。-s (skip over,跳过)选项让你通过指定在文件开头跳过多少个 字节来设置初始偏移量。例如,要从偏移量 0x120(十六进制 120)开始显示数据,用:

hexdump -C -s 0x120 /bin/ls | less

要限制输出量,你用 -n(number of bytes,字节数) 选项。下面这条命令从偏移量 0x120 开始,显示 96 个字节 的数据(即 6 行输出)。在这种情况下,输出量非常小,我们 不需要把它用管道传给 less:

hexdump -C -s 0x120 -n 96 /bin/ls

顺便说一句,正是这条命令生成了图 21-9 的输出。

把这两个选项纳入语法,我们可以为 hexdump 定义一个 更完整的规格:

hexdump -C [-s offset] [-n length] [file...]

其中 file 是文件的名字,offset 是在文件开头 跳过的字节数,length 是要显示的字节数。注意:offset 可以是任何基数,但 length 必须是十进制数。(没人知道 为什么;你自己编个理由吧。)

— 提示 —

在 FreeBSD 上,你可以把命令 hd 用作 hexdump -C 的别名。因此,在 FreeBSD 系统上, 下面两条命令是等价的:

hd /bin/ls
hexdump -C /bin/ls

如果你想在别的系统上用这个方便的命令,你只要用下面两条 命令之一创建你自己的别名即可。第一条用于 Bourne Shell 家族;第二条用于 C-Shell 家族:

alias hd='hexdump -C'
alias hd 'hexdump -C'

要让别名永久生效,把合适的命令放进你的环境文件里。 (别名在第 13 章讨论;环境文件在第 14 章讨论。)

要用 od 以规范格式显示一个二进制文件,语法是:

od -Ax -tx1z [file...]

其中 file 是一个文件的名字。

-A(address,地址)选项让你指定偏移量值用哪个计数 系统。对于规范输出,你指定 x,它以十六进制显示偏移量。

-t(type of format,格式类型)选项控制数据如何显示。 对于规范输出,你指定 x1,它一次一个字节地以十六进制 显示数据,再加上 z,它在每行末尾显示对应的 ASCII。 要查看格式代码的完整列表,见 man 手册页(man od) 或 info 文件(info od)。

(注意:这个语法是针对 GNU 版本的 od,比如你在 Linux 上会找到的那种。如果你的系统没有 GNU 工具集,命令会更原始。 特别是,你将无法使用 z 格式代码。查看你的 man 手册页 了解细节。)

举个例子,下面这条 od 命令等价于我们最初的 hexdump 命令。它以规范格式显示 ls 文件的内容:

od -Ax -tx1z /bin/ls | less

如果你想限制显示的数据量,还有两个选项可以用。-j (jump over,跳过)选项指定在文件开头跳过多少个字节。例如, 要从偏移量 0x120(十六进制 120)开始显示数据,用:

od -Ax -tx1z -j 0x120 /bin/ls | less

要限制输出量,用 -N(number of bytes,字节数)选项。 下面这条命令从偏移量 0x120 开始,显示 96 个字节(6 行输出)。 在这种情况下,输出量非常小,我们不需要把它用管道传给 less:

od -Ax -tx1z -j 0x120 -N 96 /bin/ls

这条命令生成的输出与你在图 21-9 中看到的类似。

把这两个选项纳入语法,我们可以为 od 定义一个更完整的 语法:

od -Ax -tx1z [-j offset] [-N length] [file...]

其中 file 是文件的名字,offset 是在文件开头 跳过的字节数,length 是要显示的字节数,可以是十进制、 十六进制或八进制。

— 提示 —

od 的语法复杂而别扭。不过,你可以通过创建一个别名 来指定那些产生规范格式输出的选项,从而简化操作。下面这些 命令就能办到。第一条用于 Bourne Shell 家族;第二条用于 C-Shell 家族:

alias od='od -Ax -tx1z'
alias od 'od -Ax -tx1z'

一旦你有了这样的别名,每当你键入 od,就会自动得到 你想要的输出。要让别名永久生效,把这些命令之一放进你的 环境文件里。(别名在第 13 章讨论;环境文件在 第 14 章讨论。)

名称的由来
(What's in a Name?)

Canonical(规范)


本章前面,当我们讨论交互式基于文本的程序如何处理输入时, 我讲过规范模式(canonical mode)和非规范模式 (non-canonical mode)。在本节中,我提到二进制输出的 某种格式称为规范输出。计算机科学家使用 "canonical" 一词 与常规英语含义不同,所以你应该理解这个区别。

在一般英语中,"canonical" 一词与"canon"(教规)的概念有关, 即管理基督教会成员的一套官方规则。Canonical 描述遵循教规 的事物。因此,人们可能提到天主教会的规范(canon)实践。

在数学中,同一个术语有更精确、更简练的含义。它指的是表达 一个数学想法的最简单、最重要的方式。例如,高中学生会学到 求二次方程根的规范公式。

计算机科学家从数学借用了这个术语,并且在借用时大大放宽了 它的含义。在计算机科学中,CANONICAL(规范的)指的是做某事 最常见、最惯例的方式。例如,在我们讨论 odhexdump 命令时,我们谈到了显示二进制数据的规范格式。 这种格式并没有什么神奇之处。然而,它很好用,已经被用了 四十多年,而且它是人们所期望的,所以它是"规范的"。

为什么这么多计算机术语来自数学?
(Why Does So Much Computer Terminology Come From Mathematics?)

随着你学到越来越多的计算机科学,你会注意到许多术语 源自数学。举个例子,"canonical" 这个词 — 我们在本章 用了两种不同的含义 — 就来自一个相似的数学术语。你可能 想知道为什么这么多计算机术语来自数学。原因有几个。

早期计算机科学在 20 世纪 50 和 60 年代发展起来,建立在 数学家在 20 世纪 30 和 40 年代所做的理论工作之上。特别 地,计算机科学的数学基础由 Alan Turing(1912-1954)、 John von Neumann(1903-1957)、Alonzo Church(1903-1995) 以及,在较小程度上,Kurt Gödel(1906-1978)的工作奠定。

在 20 世纪 50 和 60 年代,几乎所有计算机科学家都是数学家。 事实上,计算机科学被认为是数学的一个分支(※)。因此, 先驱们从自己领域的术语中汲取词汇来描述新思想,是很自然的事。

* 脚注

在我读本科的学校 — 加拿大滑铁卢大学 — 计算机科学系 曾经(现在仍然)是数学学院的一部分。事实上,我的本科学位 其实是数学学士,主修计算机科学。

多年来,随着计算机科学的发展,它需要大量的分析和形式化。 与其他科学一样,所需的技术和洞见都取自数学,而数学经过 两千多年的研究和形式化,在这方面工具丰富。(这就是为什么 伟大的德国数学家和科学家 Carl Friedrich Gauss 把数学称为 "科学皇后"。)即使在今天,需要抽象和逻辑推理的计算机科学家 和程序员仍然大量借用数学。在这样做的过程中,他们常常修改 数学术语以满足自己的需要。

— 提示 —

数学之于计算机科学,犹如希腊语和拉丁语之于英语。

练习
(Exercises)

复习问题 #1:

你分别用哪些程序来显示:

• 一次一屏地显示一个文本文件
• 一次性显示整个文本文件
• 文本文件的开头部分
• 文本文件的末尾部分
• 一个二进制文件

复习问题 #2:

当你用 less 显示一个文件时,执行下列动作分别用哪些命令?

• 向前翻一屏
• 向后翻一屏
• 转到第一行
• 转到最后一行
• 向前搜索
• 向后搜索
• 显示帮助
• 退出程序

复习问题 #3:

你可以用 less 显示不止一个文件,例如:

less file1 file2 file3 file4 file5

在阅读过程中,执行下列动作分别用哪些命令?

• 转到下一个文件
• 转到上一个文件
• 转到第一个文件
• 从列表中删除当前文件

要在所有文件中向前搜索和向后搜索,你分别用哪些命令?

复习问题 #4:

要监视一个不断增长的文件末尾,你会用哪条命令?

复习问题 #5:

当你显示一个二进制文件时,什么是规范格式?如何用 hexdump 以规范格式显示一个文件?用 od 呢?

运用你的知识 #1:

检查你的 PAGER 环境变量的值。如果它没有设成 less,现在就设一下。显示 less 程序本身的 man 手册页(第 9 章)。执行下列操作:

• 显示帮助信息
• 一次一屏地向下翻页直到帮助结尾
• 花点时间阅读每一屏
• 退出帮助

• 向前搜索 "help"
• 再搜索一次
• 再搜索一次
• 向后搜索

• 转到 man 手册页的末尾
• 向后翻一屏
• 转到第 100 行
• 显示当前行号
• 转到 man 手册页 20% 处的那一行
• 显示当前行号
• 转到 man 手册页的开头

• 退出

运用你的知识 #2:

你想用 less 显示文件 list 的内容,它包含 很多行文本。除了文本,你还想看到行号。然而,文本里 并没有行号,而你又不想在任何方面改动原始文件。

nlless 你会怎么做?

只用 less 你会怎么做?

nl 有什么优势吗?

运用你的知识 #3:

把下面的二进制(基数 2)数转换成八进制(基数 8)、 十六进制(基数 16)和十进制(基数 10):

1111101000100101

你必须写出演算过程。

运用你的知识 #4:

strings 命令(第 19 章)在二进制文件 /bin/ls 中查找字符串。

然后挑一个字符串,用 hexdumpod 找出 该字符串在文件中的确切位置。

进一步思考 #1:

你想用 less 在五个不同的文件中搜索,寻找某个 特定的词序列。显而易见的解决办法是像这样用 less:

less file1 file2 file3 file4 file5

然而,这要求你跟踪并操作五个不同的文件。你也可以先 把这些文件合并成一个大文件:

cat file1 file2 file3 file4 file5 | less

这会让你的工作更省力还是更麻烦?为什么?

进一步思考 #2:

在正文中,我们讨论了四种计数系统:十进制(基数 10)、 二进制(基数 2)、八进制(基数 8)和十六进制(基数 16)。 原则上,任何正整数都可以用作基数。

考虑基数 12,也称为十二进制(DUODECIMAL)。在基数 12 中, 我们使用数字位 0 到 9。对于额外的两个数字位,我们用 A 和 B。 因此,在基数 12 中,我们这样数:0、1、2、3、4、5、6、7、8、 9、A、B、10、11、12,依此类推。十进制的 22 等于基数 12 的 1A。

基数 12 相比基数 10 有哪些重要优势?提示:12 的因数比 10 多几个?这如何简化计算?

如果我们的文化用基数 12 而不是基数 10,会更好吗?

尽管有优势,我们在计算机上并不使用基数 12。大多数时候我们 使用基数 16,它复杂得多。这是为什么?

顺便说一句,如果你读过 J.R.R. Tolkein 的《魔戒》 (Lord of the Rings)系列,你会很感兴趣地知道,精灵语 使用的是一套十二进制计数系统。