Unix 之书
☰ 目录

第 7 章...

在 Unix 中使用键盘 (Using the Keyboard With Unix)

在第 6 章中,我们讨论了 GUI(图形用户界面) 与 CLI(命令行界面)之间的 区别。从本章开始,在本书 余下的部分里,我们将专注于 CLI, 也就是使用 Unix 的传统方式。

你可以用几种不同的方式使用 CLI。当你在自己的计算机上工作时, 可以使用虚拟控制台或终端窗口 (包括 Konsole 程序)。我们在 第 6 章讨论了其中的细节。当你在远程 主机上工作时,可以通过 ssh 程序 连接,它会为你充当终端仿真器。 无论你用什么方式进入 Unix 命令行, 一旦进去之后,它的用法基本上总是一样 (大体如此)。

如果你使用的是基于图形用户界面的 系统,我希望你在阅读本章之前,先熟悉 第 6 章的几个主题:虚拟控制台、 终端窗口,以及如何选择和粘贴。 在图形用户界面下,理解这些概念对于 用好 CLI 至关重要。

最早的 Unix 终端
(The First Unix Terminals)

当 Ken Thompson 和 Dennis Ritchie 最初 开发 Unix 时(见第 2 章),他们使用 Teletype ASR33 终端(见第 3 章)。 Teletype ASR33 是一台机电装置, 最初是为发送和接收文本报文而 开发的。它有一个用于输入的键盘, 还有一台用于输出的内置打印机。 它还有一台纸带穿孔机,可以通过在 纸带上打孔来存储数据;以及一台 纸带阅读器,可以从穿孔纸带上 读取数据。

ASR33 的功能使它适合用作计算机 终端。事实上,从 20 世纪 60 年代中期到 70 年代中期,几乎所有非 IBM 的计算机 系统都用 ASR33 作为控制台。 Thompson 和 Ritchie 所使用的 PDP 小型机也是如此,所以这些设备顺理成章 地成为了最早的 Unix 终端(见方框)。

Teletype ASR33

ASR33 由 Teletype 公司制造,于 1963 年推出。Teletype 33 有三个型号: RO、KSR 和 ASR。在这三个 Teletype 33 型号中,ASR 是最受欢迎的,而且遥遥领先。

RO(Receive-Only,只收)有打印机但 没有键盘。因此,它能接收报文, 却不能发送报文。

KSR(Keyboard Send-Receive,键盘收发) 既有打印机又有键盘,可以发送和接收 报文。发出的报文由人在键盘上 手工键入。

ASR(Automatic Send-Receive,自动收发) 有打印机、键盘和一台纸带穿孔机/ 阅读器。与 KSR 一样,ASR 可以发送和 接收报文。不过在 ASR 上,发出的文本 有两种生成方式。既可以人在键盘上 手工键入,也可以自动从预先打好孔的 纸带上读取(这就是"自动"一词的由来)。 正是这些功能的组合,使 ASR33 成为有用的计算机终端。

Teletype ASR 33 终端重 56 磅, 其中包括一个 12 磅重的支架。如果你在 1974 年从 DEC 购买一台,价格是 1850 美元, 另外还有 120 美元的安装费和每月 37 美元的 维护费。按 2008 年的美元计算,这台机器 要 8400 美元,安装费 550 美元, 每月维护费 170 美元。

你可以在第 3 章看到 ASR33 的照片。 Figure 3-1 和 Figure 3-2 展示的是这台机器。 Figure 3-3 是纸带穿孔机/阅读器的特写。

ASR33 的键盘最初是为发送和接收 报文而设计的,并不是为了控制 计算机的运行。因此,Thompson 和 Ritchie 不得不让 Unix 去适应 ASR33 的键盘。有意思的是,他们设计的这套 基本体系运行得非常好,直到今天 仍在使用。

正如你所料,电传打字机的键盘上有 字母表 26 个字母的按键、数字 0-9, 以及最常见的标点符号。不过,还有一些 特殊的键,用于提供发送和接收 报文所必需的功能 (见 Figure 7-1)。其中最重要的 几个键是 < Esc>、<Ctrl>、 <Shift>、<Tab> 和 <Return>。

Figure 7-1: Teletype ASR33 的键盘。

就我们的目的而言,Teletype ASR33 键盘上最有趣的键有下面这些。 在从上往下数的第二行中,RETURN 键在最右边。LINE FEED 键在 RETURN 的左边。在从下往上数的 第二行中,RUB OUT 键在右边数 第三个位置。CTRL 键在最左边。 最底下一行,两端各有一个 SHIFT 键。注意,没有退格键。

<Ctrl>(Control)键尤其有用,因为 它和 <Shift> 键一样,可以与其他键 组合成新的搭配。例如,按住 <Ctrl> 键不放,再按下某个字母或数字,你就可以 发送诸如 <Ctrl-A>、<Ctrl-B>、 <Ctrl-C> 这样的信号。

Thompson 和 Ritchie 所做的,就是把这些键的 用法纳入操作系统的整体设计之中。 为此,他们把 Unix 编写成这样一个样子: 某些信号可以用来控制一个正在 运行的程序的行为。例如,名为 intr(interrupt,中断)的信号 用于终止一个程序。要发送 intr 信号,你按下 <Ctrl-C>。

用专业术语来说,当两个事物之间存在 等价关系时,我们说它们之间存在 映射(MAPPING)。当我们建立这种等价 关系时,我们说把一个事物映射(MAP)到 另一个事物上。例如,我们说 A 被映射到 B 上,意思就是,当我们使用 A 时, 就等同于使用 B。

映射是一个重要的概念,在你使用 计算机的过程中会一再遇到。在 这里我们可以说,在 Unix 内部,<Ctrl-C> 字符被映射(MAPPED)到 intr 信号上。 这也就等于说,当我们按下 <Ctrl-C> 时, 其效果就是发送 intr 信号。

等一会儿,我们会详细讨论 Unix 的信号。 事实上,我在本章的主要目标就是讲清楚 那些重要的信号及其键位映射。不过 在此之前,我想先花一点时间 谈谈术语问题。

电传打字机与 Unix 文化
(Teletypes and the Unix Culture)

在你使用 Unix 的过程中,你会发现许多 惯例都植根于 20 世纪 70 年代的技术,那正是 最初几版 Unix 开发的年代。具体说来, Unix 的世界里充满了基于早期终端 特性的概念。这就是为什么我特意要 谈谈电传打字机(最初的终端)和 VT100(最流行的终端),无论在本章 还是在第 3 章都是如此。

在这一节里,我想稍微绕个路,提两个 你将会频繁遇到的这类惯例。

我想请你注意的第一个惯例是缩写 "tty"(读作 "tee-tee-why")。在电传 打字机使用的漫长岁月里,人们把它们 称为 TTY。这一习惯被 Unix 继承了下来, 尽管电传打字机早已退出使用,"tty" 这个词仍常被当作 Unix 终端的同义词。 尤其地,你会在 Unix 文档中、在程序和 命令的名字里频繁看到这个词。下面 举几个例子:

• 在 Unix 系统内部,每个终端都有 自己的名字。显示你的终端名字的命令是 tty。(试一下,看看会得到什么。)

stty("set tty")命令可以用来 显示或更改你的终端设置。

getty("get tty")程序用于 打开与某个终端的通信,并启动登录 过程。

我要提到的第二个惯例与"打印"这个 概念有关。电传打字机终端有两种输出 数据的方式。它们可以把数据打印在一卷 连续的 8½ 英寸宽的纸带上,供人阅读(*), 也可以把数据打在 1 英寸宽的纸带上, 供机器读取。如果你去看第 3 章的 Figure 3-2,就能同时看到打印纸卷 (在中间)和纸带卷(在左边)。

* 脚注

如果你好奇的话:电传打字机把输出 打印在连续的 8½ 英寸宽的纸卷上,纸卷 直径最大可达 5 英寸。这台机器每英寸 打印 10 个字符,最长一行 72 个字符。 行距为每英寸 6 行。打印只有一种颜色, 通常是黑色。

由于输出是打印出来的,在 Unix 内部 就形成了用 PRINT 一词来描述"输出信息" 的习惯。在当时,这是合乎情理的,因为 输出确实就是字面意义上 打印在纸上的。然而有意思的是,即使后来有了 更现代的终端、数据改为在显示器上 显示,"print"(打印)这个词依然被使用, 今天仍然是这样。

因此,在 Unix 文档中,每当你读到关于 打印数据的说法,它几乎总是指显示 数据。例如,我上面提到的 tty 命令 显示的是你终端的内部名字。如果你在 Linux 版的在线 Unix 手册中查阅这个 命令(见第 9 章),你会看到 tty 的用途 被描述为"print the file name of the terminal connected to standard input" (打印连接到标准输入的终端的文件名)。

再举一个例子。当你在 Unix 文件系统中 工作时,你当前正在使用的那个目录 称为你的"工作目录"(working directory)。(我们会在第 23 章讲解这些 概念。)显示你的工作目录名字的命令是 pwd,它是 "print working directory" (打印工作目录)的缩写。

到了这里,我们自然要问一句:如果 "print" 的意思是"显示",那么当我们确实指 "打印"时,该用什么词呢?

这个问题有两个答案。首先,在某些 情况下,"print" 就是指真正的打印, 其含义通过上下文就能看清。

另一些时候,你会看到 "line printer" (行式打印机,这个词本身也是过时的说法) 或缩写 "lp"。看到这些时,你可以把它们 当作 "printer"(打印机)的同义词。例如, 打印文件最重要的两个命令名为 lplpr。(lp 来自 System V;lpr 来自 Berkeley Unix。)

Termcap、Terminfo 与 curses
(Termcap, Terminfo and curses)

正如我在第 3 章所解释的,Unix 在设计 之初就是一个人们使用终端来访问主机 的系统。Unix 的开发者们必须克服的最重要 的问题之一是:每一种终端都有自己的 特性,使用自己的一套命令。例如,虽然 所有显示终端都有一条清屏命令,但这条 命令在各种终端上并不一定相同。

那么,如果你正在编写一个程序,而在某个 特定时刻需要清掉用户终端的屏幕,你该 怎么办?当实际要发送的命令取决于所使用 终端的类型时,你怎么知道该向终端 发送什么命令?

要求每个程序都了解每一种终端的每一条 命令是不合情理的。对软件开发人员来说, 这将是一个沉重的负担。(*) 而且,当一种 新的终端问世时又该怎么办?怎样才能 让它与现有程序正常协作?

* 脚注

早在 1980 年,大多数终端就支持远超 100 种不同的命令。

解决办法是把所有不同类型终端的描述 收集到一个数据库中。这样,当某个程序 想要向终端发送一条命令时,就可以借助 数据库中的信息,以标准化的方式完成。 (关于它的工作原理,我们稍后还会 多谈一些。)

第一个这类系统由 Bill Joy 创建,他是 Berkeley Unix 的创始人之一(见第 2 章)。 1977 年,Joy 还是一名研究生,他在整理 1BSD(Berkeley Unix 的第一个正式版本)时, 加入了一套管理各种类型终端显示屏幕的 系统。1978 年年中,他发布了 2BSD,其中 包含这套系统更精细的版本,他将其命名为 TERMCAP("terminal capabilities",终端 功能)。第一个使用 Termcap 的重要程序是 vi 编辑器(见第 22 章),同样 由 Joy 编写。

在程序内部直接使用 Termcap 是一件 很费力的事。为了让它更易用,另一位 Berkeley 学生 Ken Arnold 开发了一套 编程接口,称之为 curses。(这个名字 来自 "cursor addressing",即光标寻址。) curses 的设计目标是完成管理屏幕 显示所需的全部功能,同时把细节对 程序员隐藏起来。

程序员一旦学会使用 curses,就能写出 适用于任何类型终端的程序,甚至包括那些 尚未发明的终端。唯一的要求是,该终端 在 Termcap 数据库中有一个条目。第一个 使用 Termcap 的程序是一款流行的基于文本的 游戏,名叫 Rogue(见方框)。

curses 与 Termcap 的首次应用:
游戏 Rogue

第一个使用 curses 和 Termcap 来 控制显示屏幕的程序是 Rogue,这是一款 单人、基于文本的奇幻游戏,属于 Dungeons & Dragons 类型。

玩 Rogue 时,你扮演一名身处巨大 地宫中的冒险者。游戏开始时,你位于 地宫的最上层。你的目标是一路战斗到 地宫底部,在那里你可以拾到 Yendor 的 护身符(Amulet of Yendor)。然后你必须 带着护身符返回顶层。途中你会遇到 怪物、陷阱、暗门和财宝。

在 Rogue 开发的年代,还有另一款单人 奇幻游戏 Adventure,它在程序员中极为、 极为流行。(事实上,我记得自己是在一台 老式的 Texas Instruments 打印终端上玩它的, 那台终端通过一条缓慢的电话线连接到 一台 Unix 计算机。)Adventure 每次玩起来 都是一样的,而 Rogue 的地宫及其内容 是随机生成的。这意味着游戏每次都不 相同。此外,由于 Rogue 使用 curses, 它能够绘制简单的地图,而这是 Adventure 做不到的。

Rogue 的作者是 Michael Toy、Glenn Wichman, 后来还有 Ken Arnold 加入。第一个版本是 为 Berkeley Unix 编写的,1980 年,Rogue 随 4.2BSD 一同发布。4.2BSD 非常流行,以致 没过多久,全世界的学生都能玩到 Rogue。

如果你去看 Rogue 的第一个版本,会觉得它 简陋得难以置信。然而,它比此前任何 计算机游戏都要精细得多,在当时被大家 认为非常酷。后来,Rogue 被移植到 各种其他系统上,包括 PC、Macintosh、 Amiga 和 Atari ST。

今天,Rogue 依然存在,以现代的形式被 世界各地的人玩着。如果你有兴趣了解 Unix 早期留下的一件颇有趣味的遗产, 不妨在互联网上搜索一下 "Rogue"。

为了有效地工作,Termcap 数据库必须 包含每一种可能与 Unix 一起使用的终端的 每一个变体的技术信息,而所有这些 数据都存放在一个单独的文件里。多年来, 随着大量新终端面市,Termcap 文件不断 膨胀,大到难以维护、搜索起来也很慢。

当时,curses 正由 Bell Labs 的程序员 为 System III 增强,后来又为 System V Release 1 做增强(见 第 2 章)。为了提升 curses 的性能, Bell Labs 的程序员用一套名为 TERMINFO ("terminal information",终端信息)的新机制 取代了 Termcap。Terminfo 把数据存放在 一系列文件中,每种终端类型一个文件。 这些文件被组织在以 az 命名的 26 个目录中,全部位于一个 Terminfo 主目录之下。(读完第 23 章之后, 你就会明白这是怎么回事。)Terminfo 的设计 极其灵活,至今仍在沿用。例如,在 Linux 中,通用 VT100 终端的信息存放在名为 下面的文件里:

/usr/share/terminfo/v/vt100

Terminfo 主目录的位置在不同的系统上 可能不同。如果你想在你的系统上找到它, 最常见的名字是:

/usr/share/terminfo/
/usr/lib/terminfo/
/usr/share/lib/terminfo/
/usr/share/misc/terminfo

Terminfo 最大的问题在于,拥有 Bell Labs 的 AT&T 不肯发布源代码(见第 2 章)。这就 意味着,尽管 System V 有 Terminfo 和 更好的 curses 版本,黑客社区却 无法获得它们。他们只能凑合着用较老的、 功能较弱的基于 Termcap 的那套机制。

为了克服这一限制,1982 年,一位名叫 Pavel Curtis 的程序员开始编写 curses 的自由版本,他称之为 ncurses ("new curses",也就是"新 curses")。 ncurses 的传播范围非常有限,直到 1991 年由另一位程序员 Zeyd Ben-Halim 接手。1993 年年末,Eric Raymond 加入 Ben-Halim,两人一起认真地投入 ncurses 的开发。

在整个 20 世纪 90 年代初期,ncurses 有很多问题。但随着时间推移,随着其他 人加入这项工作,问题一一得到解决, ncurses 与 Terminfo 最终成为 经久不衰的标准。

如今,Terminfo 已经永久地取代了 Termcap。 不过,为了与非常古老的程序保持兼容, 某些 Unix 系统仍然保留着一个 Termcap 文件, 即使它已经过时、其使用已不被推荐(*)。

* 脚注

如果说某个东西"deprecated"(不推荐使用), 意思是:虽然你还能用它,但你不该用它, 因为它已经过时了。

你经常会在计算机文档中看到 "deprecated"这个词,尤其在变化飞快的 编程领域。当你看到这样的说明时,应当 把它看作一个警告:该特性可能会在产品 未来的版本中被移除。

你想看看 Termcap 或 Terminfo 信息是什么 样子吗?Termcap 数据库很容易显示,因为它 由纯文本组成,存放为一个长长的 文件。如果你的系统上有 Termcap 文件, 可以用下面这条命令显示它:

less /etc/termcap

less 程序逐屏显示一个文件的内容。 我们会在第 21 章详细讲解 less。 眼下我先告诉你,less 启动之后:

• 要向前翻一屏,按 <Space>。
• 要向后翻一屏,按 <B>。
• 要退出,按 <Q>。
• 要显示帮助,按 <H>。
• 要跳到 VT100 的 Termcap 条目,键入 /^vt100 然后按 <Return>。

(/(斜杠)字符表示"搜索",^ (脱字符)表示"在行首"。)

Terminfo 数据是编译过的(也就是说被处理成 非文本格式),这意味着你无法直接查看 它。你必须使用一个名为 infocmp 的 专用程序来读取数据,并把它还原成 纯文本。(如果你的系统不识别 infocmp 命令,大概说明没有安装 ncurses。)

使用 infocmp 时,只要指明你想查看其 信息的终端名字即可。例如,要显示 VT100 终端的 Terminfo 数据,使用命令:

infocmp vt100 | less

|(竖线)字符把命令的输出送交给 less,以便逐屏显示输出内容。

要显示你当前所用终端的 Terminfo 数据, 只写命令名而不写终端名:

infocmp | less

如果你在 PC 上运行 Linux,你会看到两种 情况之一。在终端窗口中,例如 Konsole 程序(见第 6 章),你的终端类型是 xterm,即一个 X 终端。在虚拟控制台 中(同样见第 6 章),你的终端类型是 linux,它类似于 VT220,也就是 VT100 的彩色版本。

Unix 如何知道你用的是什么类型的终端?
(How Does Unix Know What Type of Terminal You Are Using?)

从上面的讨论你可以看出,让 Unix 知道 你使用的是哪种类型的终端是很重要的。 在 20 世纪 90 年代后期之前,这可能真是 一个问题。当时有很多种不同的终端, 而你必须确切地告诉系统你用的是哪一种。 要做到这一点,你就得学会使用各种 技术性命令。

如今这已经不再必要,原因有二。第一, 许多人在自己的 PC 上使用 Unix。当你这样 做时,"终端"是内置在计算机里的, Unix 自然而然地知道它的情况。

当你连接到远程主机时 — 通过局域网、 互联网或电话线 — 你用的是终端仿真 程序,而不是真正的终端。如今,这类程序 能够告诉远程主机它们在仿真哪种终端, 所以你不必为这些细节操心。

虽然理论上程序可以仿真任何类型的终端, 但实践中今天你只会见到四种类型。 最常见的两种是 VT100 和 xterm。 VT100 是我们在第 3 章中详细讨论过的那款 著名的基于文本的终端。xterm 是 X 终端,即标准的图形终端,同样在第 3 章 讨论过。在较小程度上,你可能还会看到另 两种终端被仿真:VT220,即 VT100 的彩色 版本;以及用于 IBM 大型机的 3270。 (如果你使用 Linux,你可能会看到一种 名为 "linux" 的终端类型。它本质上 就是 VT220。)

为了记录你使用的是哪种终端,Unix 使用 所谓的 ENVIRONMENT VARIABLE(环境变量)。 环境变量是一种带有名字和值的实体, 它对 Shell 以及你运行的任何程序始终 可用。特别地,有一个名为 TERM 的 环境变量,它的值被设置为你所使用的 终端类型。

任何时候,你都可以用 echo 命令加上一个 $(美元符号)字符和环境变量的名字, 来显示某个环境变量的值。例如,要查看 TERM 变量的值,请输入:

echo $TERM

这会显示你此刻正在使用的终端类型。

我们会在第 12 章更详细地讨论环境变量。 眼下,如果你好奇的话,这里有一条命令, 可以用来显示你的 Shell 中所有环境变量 的值。

printenv

这条命令的名字 printenv 是 "print environment variables"(打印环境变量) 的缩写。你会记得,在本章前面我解释过, Unix 的习惯是用 "print"(打印)一词作为 "display"(显示)的同义词。

— 提示 —

你会记得,正如我在第 4 章所解释的, Unix 区分小写和大写字母。例如,在 Unix 中,名字 harley 与名字 Harley 是完全不同的。

由于小写字母更容易键入,人们习惯几乎只用小写来命名,包括 用户名、命令和文件。这就是为什么,例如, 你可能会看到用户名 harley,却绝不会 看到 HarleyHARLEY

这条规则的主要例外是环境变量。传统上, 在 Shell 内部,环境变量用大写名字, 例如 TERM。这让我们一眼就能看出, 我们所面对的确实是一个环境变量。

修饰键;<Ctrl> 键(The Modifier Keys; The <Ctrl> Key)

<Ctrl> 键(这个名字是 "Control" 的 缩写)是我在本章开头提到的早期电传打字机 终端上的一个特色。在 Figure 7-1 那张 电传打字机键盘的照片里,<Ctrl> 键是 从下往上第二行最左边的那个键。顾名思义, 这个键用来控制电传打字机的运行。当 Unix 被创造出来时,<Ctrl> 键被 Unix 的 开发者们采纳,并以几种重要的方式 集成进了系统,我们马上就会讲到。

要使用 <Ctrl> 键,你按住它不放 (就像 <Shift> 键那样),同时按下 另一个键,通常是一个字母。例如,你可以 按住 <Ctrl> 再按 <A> 键。

基于字母表共有 26 种这样的组合 — 从 <Ctrl>+<A> 到 <Ctrl>+<Z> — 此外还有少数几种你可能会碰到的组合。 由于一遍又一遍地写 "Ctrl" 很麻烦,Unix 社区使用一种简写记法:字符 ^ (caret,脱字符)。当你看到这个字符紧挨着 另一个字符时,它的意思是"按住 <Ctrl> 键"。例如,^A 表示按住 <Ctrl> 再按 <A> 键。你可能还会看到 <Ctrl-A> 或 C-a 这样的写法, 它们表示的是同一回事。

按照惯例,我们写 <Ctrl> 组合时总是 用大写字母。例如,我们写 ^A, 绝不写 ^a。使用大写字母能让这类 组合容易阅读:比如把 ^L^l 比较一下。不过,它并不是真正的 大写字母,所以,当你使用 <Ctrl> 组合时,不要按 <Shift> 键。

为了让你习惯这种记法,请看下面这个例子。 这是 stty 命令输出的一部分,我们在 本章后面会遇到这条命令。

erase  kill  werase  rprnt  flush  lnext
^H     ^U    ^W      ^R     ^O     ^V

susp   intr  quit  stop    eof
^Z/^Y  ^C    ^\    ^S/^Q   ^D

stty 命令的输出告诉我们,要发送某些 信号该按哪些键。细节现在并不重要。我要你 注意的是这套记法。在这个例子中,我们看到, 要发送 erase 信号,你用 ^H。 也就是说,你按住 <Ctrl> 再按 <H>。要发送 kill 信号,你用 ^U;要发送 werase,你用 ^W;以此类推。

<Ctrl> 键是一类所谓 MODIFIER KEY (修饰键)的例子。这类键是你在按另一个键 的同时需要按住的键。例如,当你键入 ^H 时,<Ctrl> 键"修饰"了 <H> 键。

在标准的 PC 键盘上,修饰键是 <Shift>、<Ctrl> 和 <Alt>。 <Shift> 有两种用途:键入大写字母, 以及键入一个双字符键上的上面那个字符。 例如,要在美国式键盘上键入 & (ampersand,& 符号),你按 <Shift-7>。<Ctrl> 用来键入 特殊的信号,正如我上面解释的那样。 <Alt> 键是修饰键中出现最晚的。 由于 Unix 开发时它还不存在,它不是标准 Unix 键盘的一部分。因此,使用标准 Unix CLI(命令行界面)时并不需要它。不过, 正如我们在第 6 章所讨论的,图形用户界面 会用到 <Alt> 键。

Unix 的键盘信号(The Unix Keyboard Signals)

最初的 Unix 设计假定人们使用终端来连接 一台宿主计算机。三十多年过去了,情况 依然如此,即使你是在自己的 PC 上运行 Unix 也一样(*)。多年来,出现过许多 不同型号的终端和许多不同的键盘,而 Unix 都能与它们协同工作。这是因为 Unix 使用一套键位映射机制,它足够灵活, 可以被调整得与任何特定键盘配合工作。

* 脚注

正如我们在第 6 章所讨论的,当你在自己 的计算机上运行 Unix 时,每一个虚拟 控制台和每一个终端窗口都是一个独立的 终端,它们全都连接到同一台宿主计算机。

为了在程序运行时控制其行为,Unix 使用 一组键盘信号。虽然这些信号是标准的, 但你实际按下以发送信号的键可以根据 需要更改。正是这一点带来了灵活性。 例如,有一个名为 intr(interrupt, 中断)的信号,它告诉一个进程中止。如果你 输入了一条需要很长时间才能完成的命令, 你可以通过向它发送 intr 信号来 停止它。

intr 信号的概念是写进 Unix 定义之内的。 而并没有写进 Unix 之内的,是你发送该 信号时实际按下的那个键。从理论上说, 你可以使用任何有效的键或键组合,而且 它在不同终端之间可以有所不同。

在大多数终端上,^C(Ctrl-C)被映射到 intr 信号。换句话说,要停止一个程序, 你按 ^C。在少数终端上,<Delete> 键被映射到 intr 信号。用这类终端时, 你按 <Delete> 来中止一个程序。无论 哪种情况,如果你不喜欢这个键位映射, 都可以更改它。

通常你不会去更改 Unix 的键位映射,但你 可以这么做,而这正是系统如此易于 适应的原因。在接下来的几节里,我会介绍 重要的键盘信号以及如何使用它们。 然后我会告诉你,如何查出你的特定终端上 用的是哪些键,以及如果你愿意,如何 更改它们。

理解键盘信号并知道如何使用它们,是使用 Unix CLI 所需的基本技能之一。

打字时使用的信号:
erasewerasekill
(Signals to Use While Typing: erase, werase, kill)

你在键入过程中可以使用三个键盘信号: erasewerasekillerase 删除你刚键入的最后一个字符; werase 删除你刚键入的最后一个单词; kill 删除整行。

要发送 erase 信号,你按 <Backspace> 或 <Delete> 键之一,具体取决于你的键盘 及其映射。看一下你键盘主键盘区右上角那个 大键。几乎在所有情况下,它就是映射到 erase 的那个键。你在键入时,按下这个键 就可以删掉刚键入的最后一个字符。

在大多数终端和 PC 上,你会使用 <Backspace> 键。在 Macintosh 上,你会使用 <Delete> 键。如果你使用的 Sun 计算机 把这两个键并排放在一起,请用 <Delete> 键(上面那个)。

— 提示 —

在大多数键盘上,你按 <Backspace> 来发送 erase 信号。在 Macintosh 上,你按 <Delete>。重要的是,你要用 在你那台特定键盘上有效的那个键。

你可能记得,在第 4 章我提到过,有些键盘 有 <Enter> 键,而另一些键盘有 <Return> 键。道理是一样的。Unix 只关心 你按键时被发送的那个信号。只要你按对了键, Unix 并不在意它叫什么名字。

在本书中,每当我提到 <Backspace> 键时, 我指的是 <Backspace> 或 <Delete>, 看你的系统用的是哪一个。同样,每当我提到 <Return> 键时,我指的是 <Return> 或 <Enter>,看你的键盘上是哪一个。

这里举一个使用 erase 信号的例子。 假设你想输入 date 命令(用来显示 时间和日期),但你把它拼错了,写成了 datx。在你按 <Return> 键之前, 先按 <Backspace>(或 <Delete>) 删掉最后一个字母,作出更正:

datx<Backspace>e

在你的屏幕上,当你按 <Backspace> 时, x 会消失。如果你想删除多个字符,可以 按任意多次 <Backspace>。

下一个信号 werase 告诉 Unix 删掉你 刚键入的最后一个单词。werase 键通常是 ^W。当你想更正刚刚键入的一个或多个 单词时,这个键很有用。当然,你也可以总是 反复按 <Backspace>,但要删除整个单词时, ^W 更快。

举个例子。你是一名间谍,想用 less 程序显示三个文件的内容,它们分别名为 datasecrettop-secret。要使用的命令是:

less data secret top-secret

你键入了这条命令,但在按下 <Return> 键 之前,你注意到另一名间谍正站在你身后, 漫不经心地朝你这边看。你决定最好不要 显示 secrettop-secret 这两个文件。按两次 ^W,删掉最后 两个单词:

less data secret top-secret^W^W

在你的屏幕上,先是 top-secret 这个词 消失,接着 secret 这个词消失。然后你 可以按 <Return> 来执行这条命令。

键入时可用的第三个信号是 killkill 键通常是 ^X^U, 取决于你的系统如何设置。这个信号告诉 Unix 删掉整行。

例如,假设你正要用我上面提到的那条命令 显示三个文件的内容。你键入了命令,但在你按 <Return> 之前,有人跑进房间告诉你银行 正在发放免费的钱。你反应很快,按 ^X(或 ^U)把命令删掉:

less data secret top-secret^X

在你的屏幕上,整行都不见了。现在你可以 注销并奔向银行。(当然,即使是要赶去领 免费的钱,你也绝不该让终端处于登录状态 就离开。)

作为备查资料,Figure 7-2 总结了键入时 使用的键盘信号。

Figure 7-2: 键入时应使用的键盘信号

信号 按键 用途
erase<Backspace>/<Delete>删掉刚键入的最后一个字符
werase^W删掉刚键入的最后一个单词
kill^X/^U删掉整行

— 提示 —

kill 键盘信号并不停止程序。它只是 删掉你正在键入的那一行。要停止一个程序, 请使用 intr 信号,它对应的是 ^C 或 <Delete>。

<Backspace> 与 <Delete> 的古怪之处
(The Strangeness of <Backspace> and <Delete>)

正如我前面提到的,Unix 的设计目标是使用 早期电传打字机终端上可用的那些基本按键: 字母表中的字母、数字、标点、<Shift> 键、一个 <Return> 键,以及 <Ctrl> 键。事实上,直到今天,你仍然 可以只靠这些基本键使用 Unix CLI。

现代键盘上还有其他键,例如 <Backspace>、<Alt>、<PageUp>、 <PageDown>、功能键、光标控制(箭头)键 等等。这些键中最有意思的是 <Backspace> 键(在 Macintosh 上是 <Delete>)。要理解这个键为什么如此 有趣,我们需要来一趟时光之旅,重访我们的 老朋友 —— 电传打字机 ASR33,也就是 最早的 Unix 终端。沿途我们也会为解开 神秘的 ^H 事件做好准备。

你会记得,电传打字机不只有键盘和打印机; 它还有一台纸带穿孔机和一台纸带阅读器。 穿孔机用来在纸带上打孔,阅读器用来读取 孔并把它们解释为数据。

纸带宽 1 英寸,每卷长 1000 英尺。纸带上的 每个位置可以通过在 8 个位置中打孔来存储 1 字节(8 位)数据。纸带每英寸可容纳 10 个字节。读取纸带时,有没有孔被解释为 二进制(以 2 为基数)数:有孔是一,无孔是零。 (如果你不懂二进制运算,不用担心。)

到这里,你可能在问自己:这些对今天的 Unix 系统有什么重要的?答案是:1970 年 纸带的物理构造,直接影响了今天你的计算机上 <Backspace> 键的工作方式。原因如下。

想象你正在使用一台电传打字机,你键入的 信息正被打在纸带上。你每键入一个字符, 机器就在纸带上打出一组孔。这些孔对应你 所键入字符的 ASCII 码(见第 19 章)。 一切顺利,直到你不小心打错了一个字符。 这时你该怎么办?

在现代的 PC 上,你只要按一下 <Backspace> 键即可。显示器上的光标会 后退一个位置,你刚键入的字符会从屏幕上 被删掉。然而,当你刚刚在一张纸上打出了 错误的孔型时,事情就没这么简单了。

解决办法分两步,并要用到特殊的电传打字机 命令。首先,你按 <Ctrl-H>,它会向纸带 穿孔机发送一个 BS(backspace,退格)命令。 这使穿孔机退回到出错的前一个位置。然后你按 <Rubout> 键,它发送 DEL 命令。这使 穿孔机在 8 个位置的每一个上都打出孔。

这样做之所以有效,是因为纸带阅读器被 设定为跳过任何 8 个孔全被打穿的字符。 用二进制的语言来说,我们说纸带阅读器会 忽略任何由全 1 组成的二进制模式。

于是,通过给某个字符叠打出 8 个孔 — 也就是说,把那里原本的二进制代码 变成 8 个 1 — 你就实际上删掉了这个 字符。这就是执行此操作的键被命名为 "Rubout"(擦除)的原因。(你可以想象, 用铅笔写错了,就用橡皮把它擦掉。)

因此,对早期 Unix 开发者来说,删掉一个 错误包含两个不同的操作:用 ^H 退格, 用 <Rubout> 擦除。他们面临的问题是: 应该把哪个键映射到 erase 信号上, 是 ^H 还是 <Rubout>?他们选择了 ^H

没过多久,计算机厂商开始生产带 <Backspace> 键的终端。为了方便,这个键 被设定成与按 ^H 相同,所以,当你打错 字时,你可以按 <Backspace> 也可以按 ^H 来更正,两者都映射到 erase 信号上。

后来,<Rubout> 键的名字被改成了 <Delete>,这是再合理不过的。最终, 一些 Unix 公司(尤其是 Sun Microsystems) 决定在键盘上加一个真正的 <Delete> 键。 与老的 <Rubout> 键一样,<Delete> 生成的是 DEL 码(原本用来在纸带上"擦除" 一个字符的那个码)。这些同样的公司随后 决定,退格时使用 DEL 码,而不再用 ^H

于是事情变成了这样:在某些键盘上,你按 <Backspace> 来删除一个字符;而在另一些 键盘上,你按 <Delete>。前一种情况下, <Backspace> 等同于 ^H,也就是在 电传打字机上发送 BS 码的那个键。后一种 情况下,<Delete> 等同于 <Rubout>, 也就是在电传打字机上发送 DEL 码的那个键。

把事情弄得更混乱的是,撰写 Unix 文档时 遇到了一个问题。BS 码 ^H 有一种简便的 表示法,但 DEL 码却没有简便的表示法。为了 解决这个问题,人们选定 ^? 这个记号来 表示 DEL。

然而与 ^H 不同,^? 并不是一个 真正的键组合。也就是说,你无法按住 <Ctrl> 键再按 ?(问号)来得到它。 ^? 只是一个两字符的缩写,它的意思 是"你的键盘上那个发送过去称作 DEL 码的键"。

更让人困惑的是,后来有些 Unix 系统被 设置成让 <Backspace> 键的效果等同于 ^?(DEL),而不是 ^H(BS)。在这种 情况下,映射到 erase 上的是 ^?, 而不是 ^H

所以现状是这样的。如果你的键盘上有 <Backspace> 键,它会被映射到 erase。PC 就是这种情况。如果你没有 <Backspace> 键,那么你会有一个 <Delete> 键被映射到 erase。 Macintosh 就是这种情况。

如果你使用的 Sun 计算机同时有 <Backspace> 和 <Delete> 两个键, 那么 <Backspace> 键等同于 ^H, 而 <Delete> 键等同于 ^?。其中 有一个键会被映射到 erase。两个都试一下, 看看哪个有效。

为了绕开 ^H/^? 的混淆,一些 Unix 系统额外定义了一个名为 erase2 的信号。 例如 FreeBSD 就是这样。

erase2erase 的效果相同,也就是 删掉你刚键入的最后一个字符。区别在于 ^H 映射到其中一个信号 — eraseerase2 — 而 ^? 映射到另一个。这样一来,你的 <Backspace> 键总能正常工作,无论它 发送的是 ^H 还是 ^?

— 提示 —

1981 年 8 月第一台 IBM PC 问世时,它配的是一副 全新的键盘。这副键盘(与我们今天使用的几乎 一样)有好几个新键,例如 <Insert>、 <Delete>、<PageUp>、 <PageDown> 等等。

重要的是要认识到,PC 键盘上的 <Delete> 键并不等同于老终端上、Macintosh 上或 Sun 计算机上的 <Delete> 键。它是一个 完全不同的键。

如果你有一台 PC,你可以自己验证这一点。 在 Unix 命令行上键入一些内容,但不要按 <Return> 键。现在按几次 <Backspace>。你会注意到最后几个字符 被删掉了。这是因为,在你的计算机上, <Backspace> 等同于 ^H^? 中的一个,看哪一个恰好被映射到 erase 上。

现在按 <Delete>(即 <Insert> 旁边 那个键)。它不会删掉前一个字符,因为它没有 被映射到 erase 信号上。

神秘的 ^H 事件(The Case of the Mysterious ^H)

你用 PC 通过网络连接到一台远程 Unix 主机。 你正在键入内容,突然,有人把一个红白相间的 塑料 Betty Boop 娃娃朝你头上扔来。你吓了一跳, 回头看屏幕时,发现自己打错了字。

你按了几次 <Backspace>,但屏幕上出现的 不是删掉最后几个字符,而是:

^H^H^H

你惊讶地看着屏幕。为什么 <Backspace> 显示的是 ^H 而不是执行删除?毕竟, ^H 不是被映射到 erase 信号上吗?

答案是:在你的计算机上,<Backspace> 等同于 ^H,而 ^H 确实被映射到 erase。这就是在你的机器上 <Backspace> 工作正常的原因。

然而,在那台远程主机上,被映射到 erase 的是 ^?。当你按 <Backspace> 键时,你发送的是 ^H, 而在远程主机上这毫无意义。这就是在远程主机上 <Backspace> 不起作用的原因。

你有四种选择。第一,你可以用其他键来纠正 打字错误。与其用 <Backspace> 一次删一个 字符,不如用 ^W 删掉整个单词,或用 ^X/^U 删掉整行。

第二,你可以找一个能向远程主机发送 ^? 的键。在许多系统上, <Ctrl-Backspace> 就能胜任。试一试, 看看是否有效。(如果你用的是 Macintosh, 试试 <Option-Backspace>。)

第三,你可以更改用来建立连接的程序的 配置。大多数通信程序都允许你控制 <Backspace> 发送的是 ^H 还是 ^?。如果你的程序允许这样做,你可以把它 配置成:每当连接到这台特定主机时, <Backspace> 发送 ^? 而不是 ^H

最后,你可以更改远程主机本身的映射,让 ^H(而不是 ^?)映射到 erase 信号上。一旦这样做了, <Backspace> 就能正常工作了。这通常是 最好的解决办法,尤其是当你要经常使用这台 远程主机时。

要做这个更改,你只需要把一条特定的命令放进 每次登录到远程主机时都会执行的初始化文件 里。命令如下。

stty erase ^H

(我们会在本章后面更详细地讨论 stty 命令。)

存放这条命令的文件名,因你所用的 Shell 而异。如果你使用 Bash(Linux 系统的默认 Shell)或 Korn Shell,就把这条命令放进你的 .profile 文件。如果你使用 C-Shell, 就把这条命令放进你的 .login 文件。 两种情况下,"."(句点)都是文件名的一部分。 (我们会在第 14 章讨论这些文件。)

停止程序:intr(Stopping a Program: intr)

有几个信号可以用来停止或暂停一个程序。 这些信号是 intrquitstopsusp。我们逐个来讨论。 有意思的是,正如你会看到的,stop 信号并不是用来停止程序的那个。

在大多数系统上,intr 键是 ^C。 在有些系统上,你改用 <Delete> 键。 两个都试一试,看看哪个对你有效。

intr(interrupt,中断)信号其实有两种 用途。第一,你可以用它把程序当场停下来。 例如,假设你输入了一条命令,它完成得实在太慢, 于是你决定不再等了。只要按 ^C。 那条命令就会中止,而你会回到 Shell 提示符。

有些程序被编写为忽略 intr 信号。在这种 情况下,总会有一种明确规定的结束程序的 办法(某种 "quit" 退出命令)。通过忽略 intr 信号,程序可以避免你因无意中 按下 ^C 而造成损害。我们说这个程序 捕获(TRAPS)了 intr 信号。

例如,想想看:当你用 vi 文本编辑器 (第 22 章)编辑一个文件时按下了 ^C, 会发生什么。vi 捕获了 intr 信号,并不会停止。要停止这个程序,你需要 使用 vi 的退出命令。如果 vi 没有捕获 intr 信号,那么按 ^C 就会中止程序,你就会丢失所有尚未保存的 数据。

注意:有时你会看到 intr 键被称作 "break"(中断)键。如果你使用 PC,你可能知道, 在 Microsoft Windows 下,^C 对命令行 程序起着 break 键的作用。你可以看到,这个 概念(和许多其他概念一样)是从 Unix 借去的。

intr 信号的第二个用途出现在你在 Shell 提示符下键入 Unix 命令时。如果你正在键入一条 命令却改变了主意,你可以按 ^C 而不按 <Return>。按 ^C 会把这条命令 彻底取消。

请务必不要把 intr 键 (^C/<Delete>)和 kill 键 (^U/^X)搞混。当你正在键入命令时, intr 取消这条命令,而 kill 擦掉该行上的所有字符。实际上,两者的结果 是一样的:你刚才键入的内容都被丢弃,你可以 重新输入一条新命令。

然而,只有 intr 能停止一个程序。 尽管名字叫 kill,它并不"杀死"什么。

停止程序的另一种方式:quit(Another Way to Stop a Program: quit)

除了 intr(^C)之外,还有另一个 键盘信号 quit 也能停止程序。quit 键通常是 ^\(<Ctrl-Backslash>, Ctrl-反斜杠)。

intrquit 有什么区别?差别 不大。在早年,quit 是为需要中止测试 程序的高级程序员设计的。当你按下 ^\ 时,它不仅停止程序,而且还告诉 Unix 制作一份 那一瞬间内存内容的副本。这些信息被存放在一个 CORE FILE(核心文件)里,也就是一个名为 core 的文件(core 是计算机内存的旧称)。 程序员随后可以用专门的工具来分析这个核心文件, 弄明白哪里出了错。

如今,程序员有了更好的调试程序的工具,而且 在大多数系统上,quit 信号不再生成 核心文件,尽管在某些编程环境中仍然用核心 文件来辅助调试。如果你并没有在积极调试某个 程序,而一个名为 core 的文件却莫名其妙 地出现在你的某个目录里,那说明你运行的某个 程序因严重错误而中止了(*)。除非你确实需要 这个文件,否则你可以把它删掉。事实上你 应该删掉它,因为核心文件相当大,没有理由 白白浪费这些空间。

* 脚注

最常见的原因是段错误(segmentation fault)。 这发生在程序试图访问并未分配给它的内存时, 例如不正确地使用了指针。(指针是一种指向 其他东西的变量。)

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

Core file(核心文件)


在计算机发展的早期,计算机内存是由机电 继电器构成的,后来又改用真空管。1952 年, 一种名为 CORE MEMORY(磁芯存储器)的新技术 首次被用于 IBM 405 Alphabetical Accounting Machine(按字母排序的会计机)的一个实验 版本。(405 是 IBM 的高端制表机,本身是一种 很老的设备,可以追溯到 1934 年。)

这种新型内存使用一种微小、圆形的中空磁性 器件构成,它们被称为 CORES(磁芯),直径约 0.25 英寸(6.4 毫米)(见 Figure 7-3)。大量 磁芯被排列成网格,每根磁芯上穿过若干根 电线。通过改变电线中的电流,可以把单个 磁芯的磁性改变为"关"或"开"。这使得 存储和读取二进制数据成为可能。

多年来,磁芯存储器不断改进,到 20 世纪 60 年代,它已成为 IBM 旗舰产品 System/360 的 支柱技术。后来技术进步了,磁芯存储器被 半导体(晶体管)和集成电路取代,由此 诞生了今天高速、高密的内存芯片。然而, 由于最早的现代计算机使用磁芯存储器,CORE 一词成了"memory"(内存)的同义词,这个 说法一直沿用至今。

在早年,调试是一件特别困难的事,尤其是当 一个程序意外中止时。程序员使用的一种技术, 是让操作系统打印出程序中止那一刻所占用的 内存内容。这被称为 CORE DUMP(核心转储), 并打印在纸上。一份核心转储可能占用许多页 纸张,而且需要很高的技巧才能解读。(*)

Unix 开发出来时,这一技术被延续了下来。 不过,Unix 不再把核心转储打印在纸上,而是 把数据保存在一个名为 core 的特殊文件 里。如果你正在测试一个程序,你可以按下 ^\(quit 键)强行让它中止并生成 一个核心文件。虽然老派的 Unix 程序员不得不 学会解读核心文件,但今天它们很少使用了, 因为已经有了好得多的调试工具。

* 脚注

我在加拿大滑铁卢大学读本科时,在计算中心 做系统程序员,那里维护着两台大型 IBM 主机。我时不时会看着那些经验更丰富的系统 程序员用一份长长的、好几页的核心转储去 追踪一个难以捉摸的错误。在我这个年轻、 未经训练的人眼里,阅读核心转储的过程既 神秘又令人敬畏。

Figure 7-3: 磁芯存储器

第一台磁芯存储器的特写照片,1952 年用于 IBM 405 Alphabetical Accounting Machine 的 一个实验版本。单个磁芯非常小,直径约 0.25 英寸(6.4 毫米)。多年来,磁芯存储器 不断改进,到 20 世纪 60 年代,它成为 IBM 旗舰计算机系列 IBM System/360 的支柱 技术。最终,磁芯存储器被半导体取代, 而半导体本身又被现代集成电路取代。

暂停显示:stopstart(Pausing the Display: stop, start)

当一个程序往屏幕底部写出一行输出,而所有 其他行都向上移动一个位置时,我们说它们 向上滚动(SCROLL upward)。如果一个程序 产生输出太快,数据会在你还来不及阅读之前 就从屏幕顶部滚出去。

如果你想看一个这样的例子,可以用下面几条 命令中的一条。dmesg 命令(我们在 第 6 章见过)会显示系统启动时显示的 所有消息。或者,你也可以用 cat 命令 显示 Termcap 文件:

dmesg
cat /etc/termcap

cat 命令(我们将在第 16 章学习)会 串联数据并把它送到默认的输出位置,也就是 "标准输出"。在这里,cat 把数据从文件 /etc/termcap 复制到你的显示器上。 然而,复制得快得惊人,大部分数据在你 读到之前就滚出了屏幕,这正是本例的 目的。

这种情况下,你有三种选择。第一,如果丢失的 数据并不重要,你可以不管它。第二,你可以 重新运行产生数据的程序,让它把输出发送给 一个所谓的分页程序,例如 less(第 21 章), 它会逐屏显示输出。这正是我们本章前面 所做的,当时我们使用了命令:

less /etc/termcap

对于 dmesg,我们会用另一条命令,它用到 |(竖线)字符。这叫做"管道符号" (pipe symbol),我们会在第 15 章讨论它。 其思路是把 dmesg 的输出改道送给 less

dmesg | less

最后,你可以按 ^S 键发送 stop 信号。这告诉 Unix 暂时暂停屏幕显示。显示一 旦暂停,你就可以按 ^Q 发送 start 信号来让它继续。要记住它们,只要想想 "S" 代表 Stop(停止),"Q" 代表 Qontinue (继续)即可。

使用 ^S^Q 可能很方便。不过, 你应当明白,^S 只是告诉 Unix 停止显示 输出。它并不会暂停正在执行的程序。程序会 继续运行,也不会停止产生输出。

Unix 会把输出暂存起来,以免丢失;而一旦你 按下 ^Q,剩下的输出就会显示出来。如果 在屏幕显示暂停期间产生了很多行新数据, 那么一旦你按 ^Q,它们很可能飞快地 一闪而过。

因此,你可能会发现,自己按 ^S^Q 的速度实在跟不上。如果是这样, 你就应该用 less 来控制输出。

顺便说一句,你可能正在纳闷:为什么选择 ^S^Q 来映射 startstop 信号?这看起来确实是个奇怪的 选择。答案是,在电传打字机 ASR33 上, <Ctrl-Q> 发送 XON 码,用来启动纸带 阅读器;<Ctrl-S> 发送 XOFF 码,用来 把它停下来。

— 提示 —

如果你的终端莫名其妙地卡住了,试着按 ^Q。你可能无意中按了 ^S, 把显示暂停了。

当一切似乎都莫名其妙地停下来时,按 ^Q 绝不会造成任何损害。

文件结束信号:eof(The End of File Signal: eof)

你时不时会遇到一些程序,它们期望你用键盘 输入数据。当你到了没有更多数据的时刻,就 按 ^D 发送 eof(end of file, 文件结束)信号来表示这一点。

举个例子:在第 8 章,我讨论了 bc 程序, 它提供内置计算器的服务。你一旦启动 bc,就可以依次输入一个个计算。每次 计算之后,bc 显示答案。当你算完时, 按 ^D 告诉 bc 没有更多数据了。 收到 eof 信号后,程序就结束运行。

Shell 与 eof 信号(The Shell and the eof Signal)

在第 2 章我解释过,Shell 是读取你的 Unix 命令并解释执行它们的程序。当 Shell 准备好 读取一条命令时,它会显示一个提示符。你键入 一条命令并按 <Return>。Shell 处理这条 命令,然后显示一个新的提示符。某些情况下, 你的命令会启动一个程序,比如一个文本 编辑器,你会与它打交道一段时间。当你结束 该程序时,你会回到 Shell 提示符。

因此,笼统地说,在 CLI(命令行界面)下进行的 一次 Unix 会话,就是一条接一条地输入 命令。

虽然 Shell 看起来神秘,它其实只不过是一个 程序。而从 Shell 的角度看,你键入的命令 只是需要处理的数据。因此,你可以通过表示 "没有更多数据"来停止 Shell。换句话说,你可以 通过按 ^D(eof 键)来停止 Shell。

但停止 Shell 到底意味着什么?它意味着你 已经完成了工作,而当 Shell 停止时,Unix 会 自动把你注销。这就是为什么按 ^D 就能 注销登录。你实际上是在告诉 Shell(以及 Unix)再没有工作要做了。

当然,这里有一个潜在的问题。如果你不小心按了 ^D 怎么办?你会立即被 注销。解决办法是告诉 Shell 捕获 eof 信号。具体怎么做取决于你用的是哪个 Shell。 我们逐个来看 — Bash、C-Shell 和 Korn Shell — 你可以在你自己的 Shell 上做实验。

Bash:捕获 eof 信号(Bash: Trapping the eof Signal)

Bash 是 Linux 的默认 Shell。要让 Bash 忽略 eof 信号,你使用一个名为 IGNOREEOF 的环境变量。(注意其中连着 两个 E,所以拼写时要小心。)它的 工作原理如下。

IGNOREEOF 被设为某个特定的数字,它 表示在把你注销之前,Bash 会在某一行的开头 忽略多少次 ^D。要设置 IGNOREEOF,使用 类似下面这样的命令。(你可以用任何你想要的 数字来代替 5。)

IGNOREEOF=5

要测试它,就反复按 ^D,数一数在你被 注销之前有多少次 ^D 被忽略了。

IGNOREEOF 已设置而你按下 ^D 时,你会看到一条消息,告诉你不能靠按 ^D 来注销。如果你正在登录 Shell 中 工作(也就是你登录时自动启动的那个 Shell), 你会看到:

Use "logout" to leave the shell.

如果你正在子 Shell 中工作(也就是你登录之后 自己启动的 Shell),你会看到:

Use "exit" to leave the shell.

如果出于某种原因你想关闭 IGNOREEOF 功能,只要把它设为 0:

IGNOREEOF=0

要显示 IGNOREEOF 的当前值,使用:

echo $IGNOREEOF

要在每次登录时自动设置 IGNOREEOF, 把相应的命令放进你的 .profile 文件 (见第 14 章)。

Korn Shell:捕获 eof 信号(Korn Shell: Trapping the eof Signal)

Korn Shell 是多种商用 Unix 系统上的默认 Shell。此外,FreeBSD 的默认 Shell 与 Korn Shell 几乎相同。

要让 Korn Shell 忽略 ^D,你设置一个 名为 ignoreeof 的 Shell 选项。(注意 其中连着两个 e,所以拼写时要小心。) 要做到这一点,使用命令:

set -o ignoreeof

一旦设置,如果你按 ^D,你会看到一条 消息,告诉你不能靠按 ^D 来注销:

Use "exit" to leave shell.

如果出于某种原因你想关闭 ignoreeof 选项,使用:

set +o ignoreeof

要显示 ignoreeof 的当前值,使用:

set -o

这会向你显示所有 Shell 选项,并告诉你它们 是关闭还是开启。

要在每次登录时自动设置 ignoreeof, 把相应的 set 命令放进你的 .profile 文件(见第 14 章)。

C-Shell:捕获 eof 信号(C-Shell: Trapping the eof Signal)

要让 C-Shell 忽略 ^D,你设置一个名为 ignoreeof 的 Shell 变量。(注意其中连着 两个 e,所以拼写时要小心。)要做到 这一点,使用命令:

set ignoreeof

一旦设置,如果你按 ^D,你会看到一条 消息,告诉你不能靠按 ^D 来注销。如果 你正在登录 Shell 中工作(也就是你登录时 自动启动的那个 Shell),你会看到:

Use "logout" to logout.

如果你正在子 Shell 中工作(也就是你登录之后 自己启动的 Shell),你会看到:

Use "exit" to leave csh.

(csh 是 C-Shell 程序的名字。)

如果出于某种原因你想关闭 ignoreeof 功能,使用:

unset ignoreeof

要显示 ignoreeof 的当前值,使用:

echo $ignoreeof

如果 ignoreeof 已设置,你什么也看不到。 如果它没有设置,你会看到:

ignoreeof: Undefined variable.

要在每次登录时自动设置 ignoreeof, 把 set 命令放进你的 .cshrc 文件(见第 14 章)。

显示键位映射:stty -a(Displaying Key Mappings: stty -a)

到目前为止,我已经提到了好几个键盘信号, 每一个都对应着你键盘上的某个键。这些 列在 Figure 7-4 中。我所显示的键位映射 是最常见的那些,但它们是能够更改的。

Figure 7-4: 重要键盘信号一览

信号 按键 用途
erase<Backspace>/<Delete>删掉刚键入的最后一个字符
werase^W删掉刚键入的最后一个单词
kill^X/^U删掉整行
intr^C停止正在运行的程序
quit^\停止程序并保存 core 文件
stop^S暂停屏幕显示
start^Q恢复屏幕显示
eof^D表示没有更多数据

要显示你系统上的键位映射,使用下面 这条命令。

stty -a

stty 是 "set terminal"(设置终端)命令; -a 的意思是"把所有设置都给我看"。

stty 命令会显示关于你终端的若干行 信息。我们唯一关心的是那些显示键盘 信号及其所映射按键的行。下面是一个来自 Linux 系统的例子:

intr = ^C; quit = ^\; erase = ^?; kill = ^U;
eof = ^D; eol = <undef>; eol2 = <undef>;
start = ^Q; stop = ^S; susp = ^Z; rprnt = ^R;
werase = ^W; lnext = ^V; flush = ^O;

下面是一个来自 FreeBSD 系统的例子:

discard = ^O; dsusp = ^Y; eof = ^D; eol = <undef>;
eol2 = ; erase = ^?; erase2 = ^H; intr = ^C;
kill = ^U; lnext = ^V; quit = ^\; reprint = ^R;
start = ^Q; status = ^T; stop = ^S; susp = ^Z; werase = ^W;

注意,FreeBSD 的例子中有一个 erase2 信号。

你可以看到,有几个信号我没有讲到。它们 大多对日常工作并不重要,你可以忽略。

— 提示 —

在第 26 章,我们会讨论如何暂停和重新启动 正在运行的程序。到那时你会看到,你可以按 ^Z 来暂停一个程序,它映射到 susp(suspend,挂起)信号。一旦你用 ^Z 暂停了一个程序,它就停止运行, 直到你输入 fg(foreground,前台) 命令把它重新启动。

所以,如果你正在干活,突然程序停了下来, 你看到类似 SuspendedStopped 的消息,那说明你不小心按了 ^Z

发生这种情况时,你只要输入 fg,你的 程序就会恢复运行。

更改键位映射:stty(Changing Key Mappings: stty)

如果你想更改某个键位映射,使用 stty 命令。只要键入 stty,后面跟上信号的 名字,再跟上新的按键指定。例如,要把 kill 键更改为 ^U,输入:

stty kill ^U

重要提示:一定要把 <Ctrl> 键组合敲成 两个分开的字符,而不是真的按出一个 <Ctrl> 组合;stty 会自己弄明白。 例如,在这个例子中,你要先键入 ^ (caret, 脱字符)字符,接着键入 U 字符。你 不要按 <Ctrl-U>。

当你在 stty 中使用 <Ctrl> 字符的 名字时,不必敲大写字母。例如,下面两条 命令都能生效:

stty kill ^u
stty kill ^U

只要记住,把两个字符分开键入即可。

严格说来,你可以把任意你想要的键映射到 某个信号上。例如,你可以把字母 K 映射到 kill 信号。下面两条命令中的 任何一条都能做到:

stty kill k
stty kill K

当然,这样的映射只会带来麻烦。每当你按 <K> 键,Unix 就会擦掉你正在键入的那一行! 拿这个来捉弄朋友一定很有乐趣。(*)

* 脚注

这话可不是我从这儿说出去的。

通常,我们只用 <Ctrl> 组合来做映射。 事实上,几乎在所有情况下,最好还是保持 原样,沿用标准的按键指定。

不过,有一种情形你可能想要做出更改。假设 你经常通过网络连接到一台远程主机,而在那台 主机上,erase 键是 ^?。然而, 你的 Backspace 键发送的是 ^H。为了让 日子更好过一些,你把 ^H 映射到 erase。这样你就能按 <Backspace> 来删除一个字符。

stty erase ^H

这里是相反的例子。你连接到一台远程主机, 那台主机上 ^H 被映射到 erase。 然而,你的 <Backspace>(或 <Delete>)键发送的是 ^?。用 stty 按下面这样更改映射:

stty erase ^?

记住,记法 ^? 指的并不是一个真正的 <Ctrl> 键组合。^? 是一个两字符的 缩写,意思是"你的键盘上那个发送 DEL 码的键"。

如果你决定拿键位映射做做实验,可以用我 上面讲过的那条命令来检查它们:

stty -a

或者,你单独输入 stty 命令:

stty

这会显示一份简略的报告,只列出那些已经 不同于默认的映射。

命令行编辑(Command Line Editing)

当你在命令行上键入时,光标指向下一个可用 位置。你每键入一个字符,光标就向右移动 一个位置。

当你出错时该怎么办?正如我们在本章前面 讨论过的,你按 <Backspace> 键,删掉一个 或多个字符,然后键入新的字符。

可是,如果你想改的错误位于行首,而错误 之后你已经键入了 20 个字符呢?你当然可以 按 21 次 <Backspace>,改正错误,再把那 20 个字符重新敲一遍。不过,有一个更轻松的 办法。

在大多数(但不是全部)Shell 中,你可以直接用 左箭头键,我这里把它称作 <Left>。你每 按一次这个键,它就把光标向左移动,而不 删除任何内容。然后你可以做出你想要的 更改,并按 <Return>。

echo 命令试试这个例子。(echo 只是把你给它的内容显示出来。)键入下面 这行:

echo "This is a test!"

现在按 <Return>。Shell 会显示 This is a test! 接着键入下面的内容, 但不要按 <Return>:

echo "Thus is a test!"

在你按 <Return> 之前,你需要把 Thus 改成 This。你的光标此刻 应该在行的末尾,所以反复按 <Left>, 直到光标正好位于 Thusu 的 右边。按一次 <Backspace> 删掉那个 u,然后键入 i。现在你可以按 <Return>,你应该会看到正确的输出。

这就是所谓命令行编辑(COMMAND LINE EDITING) 的一个例子,也就是说,在你把命令送给 Shell 之前,先更改命令行上的内容。注意,在你按 <Return> 之前,不必把光标移到行的末尾。

— 提示 —

当你按 <Return> 时,命令行上的字符会被 送给 Shell 去解释。因为光标并不产生一个 字符,所以 Shell 并不在意你送给它命令时 光标在什么位置。

这意味着,当你进行命令行编辑时,你可以从 这一行的任何位置按 <Return> 键。光标 不必非在行末不可。

所有现代 Shell 都支持某种命令行编辑,但细节 因 Shell 而异。因为这个原因,我们把大部分 讨论留到本书后面,等到逐个讲解每个 Shell 时再谈。眼下,我先教你三个最重要的技巧。 试一试,看看它们在你的 Shell 上是否 有效。

第一,在键入过程中,你可以用 <Left> 和 <Right> 箭头键在命令行内移动光标。上面 我们的例子做的就是这件事。

第二,任何时候,你都可以按 <Backspace> 删掉前一个字符。在某些 Shell 中,你还可以用 <Delete> 键删掉当前字符。(我说的 这个 <Delete> 键,是你在 PC 键盘上找到的、 位于 <Insert> 键旁边的那个。)

第三,当你输入命令时,Shell 会把它们保存在 一个看不见的"历史列表"(history list)中。 你可以用 <Up> 和 <Down> 箭头键 在这个列表中前后移动。当你按 <Up> 时, 当前命令消失,被前一条命令取代。如果你再按 一次 <Up>,就得到再前面一条命令。

因此,你可以按一次或多次 <Up> 来调出 先前的命令。如果调过头了,就按 <Down> 在列表中往下走。然后你可以按自己的心意编辑 这一行,并按 <Return> 重新提交。

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

Destructive backspace(破坏性退格),Non-destructive backspace(非破坏性退格)


当你按 <Backspace> 键时,它把光标向左 移动一个位置,同时删掉一个字符。当你按 <Left> 箭头键时,它把光标向左移动,但不删掉任何字符。

从某种意义上说,这两个动作是相似的,因为 它们都让光标后退。唯一的区别在于是否删掉了 什么。为了表达这个概念,你有时会看到 "destructive backspace"(破坏性退格)和 "non-destructive backspace"(非破坏性退格) 这两个术语。

当光标后退并且字符被删掉时,就发生了 DESTRUCTIVE BACKSPACE(破坏性退格)。你按 <Backspace> 键时出现的就是这种情况。

当光标后退而什么也没有被更改时,就发生了 NON-DESTRUCTIVE BACKSPACE(非破坏性退格)。 你按 <Left> 键时出现的就是这种情况。

Return 与 Linefeed(Return and Linefeed)

在本章前面,我们讨论了 Unix 处理 <Backspace> 键的方式如何可以追溯到 最初的 Unix 终端 — 电传打字机 ASR33。 更具体地说,有两种电传打字机码(BS 和 DEL) 与在纸带上擦除一个字符有关。Unix 的开发者们 从这两种码中选择了一种,用于 erase 信号。

有意思的是,在决定 <Return> 键应该有 什么行为时,他们面临的选择类型完全相同。 而且,他们关于 <Return> 键所做的那个 决定,结果比关于 <Backspace> 键的那个 决定重要得多。这是因为他们选定的那个码 不仅用于 <Return> 键,还被用作一个 特殊标记,放在文本文件每一行的末尾。 要开始我们的讨论,我们需要再一次回到 电传打字机 ASR33 的年代。

电传打字机 ASR33 有一个打印头,它用色带把 字符打印在纸上。随着字符被打印,打印头 从左向右移动。当打印头到达一行末尾时, 必须发生两件事。第一,纸张要向上移动一行; 第二,连在"滑架"(carriage)上的打印头要 回到最左边。

为了让电传打字机执行这些动作,在所打印的 数据中嵌入了相应的码。这些数据可以来自 键盘、来自传入的通信线路,或者来自纸带 阅读器。

第一个码是 CR(carriage return,回车),它把 滑架返回到最左边的位置。第二个码是 LF (linefeed,换行),它使纸张向上移动一行。 因此,CR-LF 这个序列完成了准备打印新的一行 所必需的动作。

从键盘上,你可以通过按 <Return> 键或 ^M 来发送 CR 码。(两者是等价的。)你 可以通过按 <Linefeed> 键或 ^J 来 发送 LF 码。(如果你回头看本章前面的 Figure 7-1,<Return> 键在从上往下 第二行的最右边。<Linefeed> 键在它左边 一个位置。)

当 Unix 的开发者们开始把电传打字机用作终端 时,他们基于 CR 和 LF 两个码创建了两个信号。 CR 码成了 RETURN 信号。LF 码成了 LINEFEED 信号。

那么现在,我们来问一个问题:当你在 Unix 终端上键入时,你按下 <Return> 键会 发生什么?在回答这个问题之前,我需要先 谈谈 Unix 如何把纯文本组织成文件。

Newline 的重要性(The Importance of Newline)

正如我们讨论过的,Unix 使用两个基于老式 电传打字机的信号:return(回车)和 linefeed(换行)。从键盘上,你按 ^M 发送 return,按 ^J 发送 linefeed。

由于 return 和 linefeed 实际上就是 ^M^J,我们通常把它们称作 字符,而不是信号。下面说明它们在三种 不同情形下的用法。

第一:当文件包含文本数据时,我们通常把数据 划分成行。在 Unix 中,我们用 ^J 字符 标记每一行的结束。当我们这样使用 ^J 时,我们把它称作 newline(换行符)字符, 而不叫 linefeed。因此,当一个程序从文件 读取数据时,它一旦遇到 newline(也就是一个 ^J 字符),就知道自己已经到达行末。

第二:当你在终端上键入字符时,你在一行结束 处按 <Return>。这样做的结果是发送 return 字符,也就是 ^M

第三:当数据显示出来时,它是一行一行地送到 你的终端的。在每一行结束时,光标必须移到 下一行的开头。与电传打字机一样,这涉及两个 分开的动作:一次"carriage return"(回车), 把光标移到行首;随后一次"line feed"(换行), 把光标向下移动一行。对于"carriage return", Unix 发送一个 return 字符(也就是 ^M)。对于"linefeed",Unix 发送一个 linefeed 字符(也就是 ^J)。因此,当 数据显示时,每一行都必须以 ^M^J 结尾。

Unix 最优雅的特性之一是:在键盘上键入的数据 与从文件读取的数据被同等对待。例如,假设你 有一个程序,它读取一系列名字,每行一个。 这样的程序既可以从你磁盘上的文件读取这些 名字,也可以从键盘读取。程序不需要以某种 特殊的方式编写就能拥有这样的灵活性。这个 特性叫做"标准输入"(standard input),它 内置于 Unix 之中。标准输入让所有 Unix 程序 都能以同样的方式读取数据,而不必担心数据 来自何处。(我们会在第 15 章讨论这个概念。)

为了让标准输入正常工作,每一行数据都必须 以 newline 结尾。然而,当你在键盘上键入 字符时,它们到达时行末带着的是 return, 而不是 newline。这就产生了一个问题。

类似地,当 Unix 程序输出数据时,它们可以使用 "标准输出"(standard output)。这让所有 程序都能以同样的方式写出数据,而不必担心 数据要到哪里去。

当数据写入文件时,每一行必须以 newline 字符 (也就是 ^J)结尾。然而,当数据写入 终端时,每一行必须以 return+newline (^M^J)结尾。这就产生了第二个问题。

这些问题通过两种办法协调解决。第一,在你 键入时,每当你按 <Return>,Unix 就把 return 变成 newline。也就是说,它把 ^M 变成 ^J

第二,当数据被写入终端时,Unix 把每个 newline 变成 return+linefeed。也就是说,它把 ^J 变成 ^M^J

下面是一个简短的总结,帮你把这一切理顺:

  1. return = ^M
  1. linefeed = newline = ^J
  1. 一般而言,每一行文本都必须以 newline 结尾。
  1. 当你按 <Return> 键时,它发送一个 return 字符,Unix 会自动把它变成一个 newline。
  1. 要在终端上显示数据,每一行都必须以这样 一组字符结尾:return+linefeed。因此,当数据 从文件送往终端显示时,Unix 会自动把每行 末尾的 newline 变成 return+linefeed。

一开始,这一切也许显得有点令人困惑。到后来 你会发现它完全合乎逻辑,到那时你就会知道, 自己终于开始用 Unix 的方式思考了。

— 提示 —

在文本文件内部,Unix 用一个 ^J (newline)字符标记每一行的结尾。然而 Microsoft Windows 的做法不同。Windows 用一个 ^M^J 标记每行的结尾。(用 Unix 的术语 说,那就是 return+linefeed。)

因此,当你把文本文件从 Unix 复制到 Windows 时,每个 ^J 都必须变成 ^M^J。 反之,当你把文件从 Windows 复制到 Unix 时, 每个 ^M^J 都必须变成 ^J

当你用一个程序在这样两台计算机之间复制文件 时,该程序应当知道如何自动为你完成这些 转换。如果它做不到,也有现成的实用程序 可以胜任。

^J 的一个重要用途:
stty sanereset
(An Important Use for ^J: stty sane, reset)

除非你是程序员,否则其实没有必要把 return 和 newline 相关的技术细节全都搞透。只要记住 在每行结束时按 <Return>,让 Unix 去 干活就行了。

不过,在某些情形下,理解这些概念会有帮助。 偶尔,你终端的设置可能被搞得乱七八糟,以致 终端无法正常工作。这种情况下,有两条命令 可以把你的终端设置重设为合理的值: stty sanereset

极少数情况下,你可能会发现,当你想按 <Return> 来输入这两条命令中的一条时, return 到 newline 的转换却不起作用,Unix 不接受这条命令。如果出现这种情况,你只要 改按 ^M 而不按 <Return>。这样 会有效,因为这两个键本质上是同一个键。

解决办法是按 ^J(等同于 newline), 这本来就是 Unix 唯一想要的。因此,当别的 办法都行不通时,键入下面两条命令中的某一条 也许能让你的终端焕发青春。一定要在命令 前后各键入一个 ^J。如果你愿意,现在 就可以试试;它不会造成任何损害。

<Ctrl-J>stty sane<Ctrl-J>
<Ctrl-J>reset<Ctrl-J>

你可能会问:既然这样,那么任何时候你都可以 按 ^J 而不按 <Return> 来输入 命令吗?当然可以 — 试试看。

为了让你看看这些命令有多有用,这里讲一件 真事。

我有个朋友叫 Susan,她当时在帮某人安装 Linux。他们正在用一个程序,它让你选择希望 包含在内核里的选项。该程序需要一个并不 存在的目录,于是 Susan 按了 ^Z 来暂停 程序。她现在有机会去创建那个目录了。

然而,恰好那个程序 — 为了不让显示画面 发生变化 — 已经取消了 return 字符的 作用。这就意味着,无论 Susan 什么时候输入 命令,输出都无法正常显示。

(记住,当 Unix 把数据写入终端时,它会在 每一行末尾放上 return+linefeed。你猜, 如果只有 linefeed 起作用,会发生什么?)

不过,Susan 这个人,要说有什么特点,那就是 足智多谋。她输入了 reset 命令,转眼间, 终端就恢复正常了。然后她创建了所需的那个 目录,重启安装程序,从此幸福地生活下去。

程序员与公主的寓言
(The Fable of the Programmer and the Princess)

很久以前,住着一位年轻、英俊、迷人的程序员 (光凭这点你就知道这是个寓言),他赢得了一位 美丽公主的爱情。然而,在他们婚礼的前夜, 公主被绑架了。

幸运的是,公主镇定自若,从她的项链上留下一串 珍珠作为踪迹。程序员沿着踪迹来到无法无天的 硅谷的一处偏远角落,他发现他的爱人被一个 邪恶的市场营销副总裁囚禁在一个废弃的技术 支持中心里。

程序员反应敏捷,拿起一块强力磁铁就进了楼。 他追踪到公主,闯进了那位市场营销副总裁正 对着惊恐失措的姑娘得意狞笑的房间。

"立刻放了那个姑娘,"程序员吼道,"否则我就用 这块磁铁把你所有的磁盘都搅乱。"

副总裁按下一个秘密按钮,眨眼之间,房间里又 冲进四个更丑陋、更魁梧的副总裁。

"话又说回来,"程序员说,"也许我们可以做笔交易。"

"你有什么想法?"副总裁说。

"你想出任何一道 Unix 题目来考我,"程序员 回答说。"如果我做出来了,我和公主就获得自由。 如果我失败了,我就离开,永不回头,公主归你。"

"一言为定,"副总裁说,他那双眼睛闪闪发光, 好似两颗裹在油脂里的红色肉块。"坐到这台 终端前。你的任务有两部分。第一,用一条 命令显示时间和日期。"

"小意思,"程序员说着,键入 date 并按下 <Return> 键。

"现在,"副总裁说,"再做一遍。"然而,当程序员 再次键入 date 时,副总裁补充道, "—不过这一次,你既不准用 <Return> 键,也不准用 ^M。"

"去看手册吧,你这个无知的蠢货!"程序员喊道, 随即按下了 ^J,一把拉起公主,带着她 奔向早已等候在那里的法拉利,过上了自由的 生活。

练习
(Exercises)

复习问题 #1:

为什么用缩写 "tty" 来指称终端会成为 Unix 的一个惯例?

复习问题 #2:

为什么用 "print"(打印)一词来指在显示器上 显示数据会成为 Unix 的一个惯例?

复习问题 #3:

术语 "deprecated"(不推荐使用)是什么 意思?

复习问题 #4:

Unix 怎么知道你在用哪一台终端?

复习问题 #5:

你要删掉刚键入的最后一个字符时按哪个键? 删最后一个单词呢?删整行呢?

应用你的知识 #1:

默认情况下,erase 键就是 <Backspace> 键(在 Macintosh 上是 <Delete> 键)。通常,这个键映射到 ^H,较少情况下映射到 ^?。 用 stty 命令把 erase 键改为 大写字母 "X"。

做到这一点之后,你按 "X" 就能删掉刚键入的 最后一个字符。试一下验证这一点。

当你按下小写的 "x" 时会发生什么?为什么?

现在用 sttyerase 键改回 <Backspace>(或 <Delete>)键。 测试一下,确认它生效了。

进一步思考 #1:

注销的一种办法是在 Shell 提示符下按 ^D(它发送 eof 信号)。由于你可能不小心这样做, 你可以告诉 Shell 忽略 eof 信号。 为什么这不是默认设置?

这件事告诉你,使用 Unix 的是些什么样的 人?

进一步思考 #2:

在第 1 章,我提到第一版 Unix 是由 Ken Thompson 开发的,为了让他能运行一个名为 Space Travel 的程序。在本章,我解释了第一个 使用 Termcap(终端信息数据库)和 curses(终端管理接口)的程序,是一款 名为 Rogue 的基于文本的奇幻游戏,由 Michael Toy 和 Glenn Wichman 编写。

创造一个全新的操作系统,以及用一套全新的 接口做实验,都是极其耗时、极其困难的 工作。你认为,是什么动力促使 Thompson, 以及后来的 Toy 和 Wichman,为了似乎微不足道的 理由去承担这样富有挑战性的工作?

如果你管理着一群程序员,你认为除了金钱 之外,他们还会被哪些动力所驱动?