|
Harley Hahn 的
|
|
各章...
命令
教师 |
第 7 章... 在 Unix 中使用键盘 (Using the Keyboard With Unix)
在第 6 章中,我们讨论了 GUI(图形用户界面) 与 CLI(命令行界面)之间的 区别。从本章开始,在本书 余下的部分里,我们将专注于 CLI, 也就是使用 Unix 的传统方式。 你可以用几种不同的方式使用 CLI。当你在自己的计算机上工作时, 可以使用虚拟控制台或终端窗口 (包括 Konsole 程序)。我们在 第 6 章讨论了其中的细节。当你在远程 主机上工作时,可以通过 ssh 程序 连接,它会为你充当终端仿真器。 无论你用什么方式进入 Unix 命令行, 一旦进去之后,它的用法基本上总是一样 (大体如此)。 如果你使用的是基于图形用户界面的 系统,我希望你在阅读本章之前,先熟悉 第 6 章的几个主题:虚拟控制台、 终端窗口,以及如何选择和粘贴。 在图形用户界面下,理解这些概念对于 用好 CLI 至关重要。
当 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>。
<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 的过程中,你会发现许多 惯例都植根于 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"(打印机)的同义词。例如, 打印文件最重要的两个命令名为 lp 和 lpr。(lp 来自 System V;lpr 来自 Berkeley Unix。)
正如我在第 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 的首次应用:
第一个使用 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 把数据存放在 一系列文件中,每种终端类型一个文件。 这些文件被组织在以 a 到 z 命名的 26 个目录中,全部位于一个 Terminfo 主目录之下。(读完第 23 章之后, 你就会明白这是怎么回事。)Terminfo 的设计 极其灵活,至今仍在沿用。例如,在 Linux 中,通用 VT100 终端的信息存放在名为 下面的文件里: /usr/share/terminfo/v/vt100 Terminfo 主目录的位置在不同的系统上 可能不同。如果你想在你的系统上找到它, 最常见的名字是:
/usr/share/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>。
|
|
当一个程序往屏幕底部写出一行输出,而所有 其他行都向上移动一个位置时,我们说它们 向上滚动(SCROLL upward)。如果一个程序 产生输出太快,数据会在你还来不及阅读之前 就从屏幕顶部滚出去。 如果你想看一个这样的例子,可以用下面几条 命令中的一条。dmesg 命令(我们在 第 6 章见过)会显示系统启动时显示的 所有消息。或者,你也可以用 cat 命令 显示 Termcap 文件:
dmesg
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 来映射 start 和 stop 信号?这看起来确实是个奇怪的 选择。答案是,在电传打字机 ASR33 上, <Ctrl-Q> 发送 XON 码,用来启动纸带 阅读器;<Ctrl-S> 发送 XOFF 码,用来 把它停下来。 提示 如果你的终端莫名其妙地卡住了,试着按 ^Q。你可能无意中按了 ^S, 把显示暂停了。 当一切似乎都莫名其妙地停下来时,按 ^Q 绝不会造成任何损害。
你时不时会遇到一些程序,它们期望你用键盘 输入数据。当你到了没有更多数据的时刻,就 按 ^D 发送 eof(end of file, 文件结束)信号来表示这一点。 举个例子:在第 8 章,我讨论了 bc 程序, 它提供内置计算器的服务。你一旦启动 bc,就可以依次输入一个个计算。每次 计算之后,bc 显示答案。当你算完时, 按 ^D 告诉 bc 没有更多数据了。 收到 eof 信号后,程序就结束运行。
在第 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 是 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 是多种商用 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 忽略 ^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 章)。
到目前为止,我已经提到了好几个键盘信号, 每一个都对应着你键盘上的某个键。这些 列在 Figure 7-4 中。我所显示的键位映射 是最常见的那些,但它们是能够更改的。 Figure 7-4: 重要键盘信号一览
要显示你系统上的键位映射,使用下面 这条命令。 stty -a stty 是 "set terminal"(设置终端)命令; -a 的意思是"把所有设置都给我看"。 stty 命令会显示关于你终端的若干行 信息。我们唯一关心的是那些显示键盘 信号及其所映射按键的行。下面是一个来自 Linux 系统的例子:
intr = ^C; quit = ^\; erase = ^?; kill = ^U;
下面是一个来自 FreeBSD 系统的例子:
discard = ^O; dsusp = ^Y; eof = ^D; eol = <undef>;
注意,FreeBSD 的例子中有一个 erase2 信号。 你可以看到,有几个信号我没有讲到。它们 大多对日常工作并不重要,你可以忽略。 提示 在第 26 章,我们会讨论如何暂停和重新启动 正在运行的程序。到那时你会看到,你可以按 ^Z 来暂停一个程序,它映射到 susp(suspend,挂起)信号。一旦你用 ^Z 暂停了一个程序,它就停止运行, 直到你输入 fg(foreground,前台) 命令把它重新启动。 所以,如果你正在干活,突然程序停了下来, 你看到类似 Suspended 或 Stopped 的消息,那说明你不小心按了 ^Z。 发生这种情况时,你只要输入 fg,你的 程序就会恢复运行。
如果你想更改某个键位映射,使用 stty 命令。只要键入 stty,后面跟上信号的 名字,再跟上新的按键指定。例如,要把 kill 键更改为 ^U,输入: stty kill ^U 重要提示:一定要把 <Ctrl> 键组合敲成 两个分开的字符,而不是真的按出一个 <Ctrl> 组合;stty 会自己弄明白。 例如,在这个例子中,你要先键入 ^ (caret, 脱字符)字符,接着键入 U 字符。你 不要按 <Ctrl-U>。 当你在 stty 中使用 <Ctrl> 字符的 名字时,不必敲大写字母。例如,下面两条 命令都能生效:
stty kill ^u
只要记住,把两个字符分开键入即可。 严格说来,你可以把任意你想要的键映射到 某个信号上。例如,你可以把字母 K 映射到 kill 信号。下面两条命令中的 任何一条都能做到:
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 这会显示一份简略的报告,只列出那些已经 不同于默认的映射。
当你在命令行上键入时,光标指向下一个可用 位置。你每键入一个字符,光标就向右移动 一个位置。 当你出错时该怎么办?正如我们在本章前面 讨论过的,你按 <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>, 直到光标正好位于 Thus 中 u 的 右边。按一次 <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> 键时出现的就是这种情况。
在本章前面,我们讨论了 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 如何把纯文本组织成文件。
正如我们讨论过的,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。 下面是一个简短的总结,帮你把这一切理顺:
一开始,这一切也许显得有点令人困惑。到后来 你会发现它完全合乎逻辑,到那时你就会知道, 自己终于开始用 Unix 的方式思考了。 提示 在文本文件内部,Unix 用一个 ^J (newline)字符标记每一行的结尾。然而 Microsoft Windows 的做法不同。Windows 用一个 ^M^J 标记每行的结尾。(用 Unix 的术语 说,那就是 return+linefeed。) 因此,当你把文本文件从 Unix 复制到 Windows 时,每个 ^J 都必须变成 ^M^J。 反之,当你把文件从 Windows 复制到 Unix 时, 每个 ^M^J 都必须变成 ^J。 当你用一个程序在这样两台计算机之间复制文件 时,该程序应当知道如何自动为你完成这些 转换。如果它做不到,也有现成的实用程序 可以胜任。
除非你是程序员,否则其实没有必要把 return 和 newline 相关的技术细节全都搞透。只要记住 在每行结束时按 <Return>,让 Unix 去 干活就行了。 不过,在某些情形下,理解这些概念会有帮助。 偶尔,你终端的设置可能被搞得乱七八糟,以致 终端无法正常工作。这种情况下,有两条命令 可以把你的终端设置重设为合理的值: stty sane 或 reset。 极少数情况下,你可能会发现,当你想按 <Return> 来输入这两条命令中的一条时, return 到 newline 的转换却不起作用,Unix 不接受这条命令。如果出现这种情况,你只要 改按 ^M 而不按 <Return>。这样 会有效,因为这两个键本质上是同一个键。 解决办法是按 ^J(等同于 newline), 这本来就是 Unix 唯一想要的。因此,当别的 办法都行不通时,键入下面两条命令中的某一条 也许能让你的终端焕发青春。一定要在命令 前后各键入一个 ^J。如果你愿意,现在 就可以试试;它不会造成任何损害。
<Ctrl-J>stty sane<Ctrl-J>
你可能会问:既然这样,那么任何时候你都可以 按 ^J 而不按 <Return> 来输入 命令吗?当然可以 试试看。 为了让你看看这些命令有多有用,这里讲一件 真事。 我有个朋友叫 Susan,她当时在帮某人安装 Linux。他们正在用一个程序,它让你选择希望 包含在内核里的选项。该程序需要一个并不 存在的目录,于是 Susan 按了 ^Z 来暂停 程序。她现在有机会去创建那个目录了。 然而,恰好那个程序 为了不让显示画面 发生变化 已经取消了 return 字符的 作用。这就意味着,无论 Susan 什么时候输入 命令,输出都无法正常显示。 (记住,当 Unix 把数据写入终端时,它会在 每一行末尾放上 return+linefeed。你猜, 如果只有 linefeed 起作用,会发生什么?) 不过,Susan 这个人,要说有什么特点,那就是 足智多谋。她输入了 reset 命令,转眼间, 终端就恢复正常了。然后她创建了所需的那个 目录,重启安装程序,从此幸福地生活下去。
很久以前,住着一位年轻、英俊、迷人的程序员 (光凭这点你就知道这是个寓言),他赢得了一位 美丽公主的爱情。然而,在他们婚礼的前夜, 公主被绑架了。 幸运的是,公主镇定自若,从她的项链上留下一串 珍珠作为踪迹。程序员沿着踪迹来到无法无天的 硅谷的一处偏远角落,他发现他的爱人被一个 邪恶的市场营销副总裁囚禁在一个废弃的技术 支持中心里。 程序员反应敏捷,拿起一块强力磁铁就进了楼。 他追踪到公主,闯进了那位市场营销副总裁正 对着惊恐失措的姑娘得意狞笑的房间。 "立刻放了那个姑娘,"程序员吼道,"否则我就用 这块磁铁把你所有的磁盘都搅乱。" 副总裁按下一个秘密按钮,眨眼之间,房间里又 冲进四个更丑陋、更魁梧的副总裁。 "话又说回来,"程序员说,"也许我们可以做笔交易。" "你有什么想法?"副总裁说。 "你想出任何一道 Unix 题目来考我,"程序员 回答说。"如果我做出来了,我和公主就获得自由。 如果我失败了,我就离开,永不回头,公主归你。" "一言为定,"副总裁说,他那双眼睛闪闪发光, 好似两颗裹在油脂里的红色肉块。"坐到这台 终端前。你的任务有两部分。第一,用一条 命令显示时间和日期。" "小意思,"程序员说着,键入 date 并按下 <Return> 键。 "现在,"副总裁说,"再做一遍。"然而,当程序员 再次键入 date 时,副总裁补充道, "不过这一次,你既不准用 <Return> 键,也不准用 ^M。" "去看手册吧,你这个无知的蠢货!"程序员喊道, 随即按下了 ^J,一把拉起公主,带着她 奔向早已等候在那里的法拉利,过上了自由的 生活。
复习问题 #1: 为什么用缩写 "tty" 来指称终端会成为 Unix 的一个惯例? 复习问题 #2: 为什么用 "print"(打印)一词来指在显示器上 显示数据会成为 Unix 的一个惯例? 复习问题 #3: 术语 "deprecated"(不推荐使用)是什么 意思? 复习问题 #4: Unix 怎么知道你在用哪一台终端? 复习问题 #5: 你要删掉刚键入的最后一个字符时按哪个键? 删最后一个单词呢?删整行呢? 应用你的知识 #1: 默认情况下,erase 键就是 <Backspace> 键(在 Macintosh 上是 <Delete> 键)。通常,这个键映射到 ^H,较少情况下映射到 ^?。 用 stty 命令把 erase 键改为 大写字母 "X"。 做到这一点之后,你按 "X" 就能删掉刚键入的 最后一个字符。试一下验证这一点。 当你按下小写的 "x" 时会发生什么?为什么? 现在用 stty 把 erase 键改回 <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,为了似乎微不足道的 理由去承担这样富有挑战性的工作? 如果你管理着一群程序员,你认为除了金钱 之外,他们还会被哪些动力所驱动?
© 本书全部内容,2026 年版权所有,Harley Hahn
|