终端中的 ASCII 控制字符
作者:Julia Evans | 2024年10月31日
我最近一直在思考终端的问题,昨天我对所有的"控制代码"(Control Codes,如 Ctrl-A、Ctrl-C、Ctrl-W 等)产生了好奇。它们到底是怎么回事?
ASCII 控制字符表
这里有一张包含所有 33 个 ASCII 控制字符的表格,以及它们在我的机器(macOS)上大致的用途。虽然这里面有无数的例外情况,但我会讨论一下它的含义以及我知道的该图表存在的所有问题。

不同种类的代码混在一起
这张图表让我感到惊讶的第一点是,这 33 个控制代码被(非常粗略地)分成了以下几类:
- 由操作系统终端驱动程序处理的代码 :例如,当操作系统看到
3(Ctrl-C) 时,它会向当前程序发送SIGINT信号。 - 其余代码则按原样传递给应用程序 :应用程序可以随心所欲地处理它们。其中一些子分类包括:
- 对应键盘按键的代码 :例如
Enter、Tab、Backspace。例如,当你按下Enter时,你的终端会收到13。 - Readline 使用的代码:"应用程序可以随心所欲"通常意味着"它或多或少会执行 readline 库所做的操作,无论应用程序是否真的使用了 readline"。所以我标记了一堆 readline 使用的代码。
- 其他代码 :例如,我认为
Ctrl-X在终端中通常没有标准含义,但 Emacs 非常频繁地使用它。
- 对应键盘按键的代码 :例如
这些代码没有真正的结构,它们是随机散布的,因为这是有机演化而来的。
只有 33 个控制代码
另一件让我惊讶的事情是,只有 33 个控制代码------A 到 Z,再加上 7 个(@, [, \, ], ^, _, ?)。这意味着,如果你想在终端应用程序中使用 Ctrl-1 作为快捷键,那通常没有意义------至少在我的机器上,Ctrl-1 和直接按 1 是一样的,Ctrl-3 和 Ctrl-[ 也是一样的。
此外,Ctrl+Shift+C 并不是一个控制代码------它的作用取决于你的终端模拟器。在 Linux 上,Ctrl-Shift-X 通常被终端模拟器用于复制、打开新标签页或粘贴,它根本不会发送到 TTY。另外,我经常使用 Ctrl+Left Arrow,但那也不是控制代码,它发送的是一个 ANSI 转义序列(ctrl-[[1;5D),这是另一回事,我们在这篇文章中没有空间讨论。这种"只有 33 个代码"的情况与 GUI 中的键盘快捷键完全不同,在 GUI 中你可以为任何按键设置 Ctrl+KEY。
官方的 ASCII 名称对我来说意义不大
这 33 个控制代码在 ASCII 中都有名字(例如 3 是 ETX)。当这些代码最初被定义时,它们根本不是用于计算机或终端的,而是用于电报机。电报机与 UNIX 终端不同,所以很多代码被重新定义了。我个人觉得这些 ASCII 名称没什么用,因为 50% 的情况下,ASCII 中的名称与该代码在当今 UNIX 系统上的实际作用毫无关系。所以忽略这些名称会更容易。
很难将 Ctrl-M 用作键盘快捷键
另一个奇怪的地方是 Ctrl-M 实际上就是 Enter,Ctrl-I 就是 Tab,这使得很难将它们用作快捷键。根据快速研究,似乎有些人确实还在使用 Ctrl-I 和 Ctrl-M 作为快捷键,但这需要你配置终端模拟器来区别对待它们。我的主要结论是:如果我写一个终端应用程序,我应该避免使用 Ctrl-I 和 Ctrl-M 作为快捷键。
如何识别发送了哪些控制代码
在写这篇文章时,我需要进行大量实验来弄清楚各种按键组合的作用,所以我写了这个 Python 脚本 echo-key.py 来打印它们。
关于规范模式与非规范模式的注意事项
表中的两个代码(Ctrl-W 和 Ctrl-U)被标记为"由操作系统处理",但实际上它们并不总是由操作系统处理,这取决于终端是否处于"规范模式"(canonical mode)还是"非规范模式"(noncanonical mode)。
- 在规范模式 下,程序仅在按下
Enter后才获得输入(OS 负责处理Backspace或Ctrl-W)。 - 在非规范模式 下,程序在按下按键时立即获得输入,
Ctrl-W和Ctrl-U会被传递给程序处理。
关于"所有 OS 终端驱动程序代码都可以通过 stty 配置"
我提到 Ctrl-C 发送 SIGINT,但如果愿意,你可以使用 stty 工具重新映射所有标记为"OS 终端驱动程序"的代码。我个人从未重新映射过这些,也无法想象这样做的理由。
关于信号的注意事项
- 如果关闭了
ISIG终端模式,OS 就不会发送信号。例如,Vim 会关闭ISIG。 - 在 BSD 系统上,有一个额外的控制代码
Ctrl-T,它发送SIGINFO。
存在很多冲突
由于"只有 33 个代码",系统中不同部分想要使用同一个代码的情况很多。例如,默认情况下 Ctrl-S 会冻结屏幕,但如果你关闭它,readline 就会用 Ctrl-S 进行向前搜索。
关于"Backspace"和"其他 Backspace"
在图表中,我将代码 127 标记为"Backspace",将 8 标记为"其他 Backspace"。这似乎是 Mastodon 回复中讨论最多的主题。
- 在我的机器上:我按下
Backspace键,TTY 收到字节127(DEL)。 - 如果我按
Ctrl+H,在 readline 中它的效果与Backspace相同,但在没有 readline 支持的程序中,它只会打印出^H。
总结
我可能忽略了更多关于"它在我的机器上如何工作"与"在别人机器上如何工作"的区别。虽然我觉得这些内容很有趣,但我不认为它们一定很有用。我在过去 20 年里成功地每天使用终端,而无需了解这些细节。但我很高兴能学习它,也许你也会觉得有趣。