ANSI 转义码标准
- 作者: Julia Evans
- 来源: Julia Evans's Blog
- 阅读:72
- 发布: 2025-08-22 14:47
- 最后更新: 2025-08-22 14:47
大家好!今天我想谈谈 ANSI 转义码。
很长一段时间里,我只是模糊地知道 ANSI 转义码(就是那种可以在终端让文本变红之类的东西),但我并不真正了解它们应该在哪里定义,也不清楚是否有相关标准。我只是对它们有种模糊的 “这里有未知风险” 的感觉。而在今年学习终端相关知识时,我了解到:
-
ANSI 转义码为终端的可用性带来了很多改进(你知道通过 SSH 连接到远程机器时,有办法可以把内容复制到系统剪贴板吗?这是一个名为 OSC 52 的转义码!)。
-
它们并没有完全标准化,正因如此,其工作效果并不总是可靠。而且由于它们是不可见的,所以排查转义码问题时会极其令人沮丧。
所以我想为自己整理一份有关转义码现有标准的清单,因为我想知道,使用它们是否就得面临不可靠和令人沮丧的情况,还是说未来我们能够更放心地使用它们。
什么是转义码?
你是否曾在终端中按下左箭头键,然后看到过 ^[[D?这就是一个转义码!它被称作 “转义码” 是因为第一个字符是 “转义” 字符,通常写作 ESC
、\x1b
、\E
、\033
或者 ^[
。
转义码是终端模拟器与在终端中运行的程序来传递各类信息(颜色、鼠标移动等)的方式。转义码有两种类型:
- 输入码:终端模拟器针对无法用 Unicode 表示的按键操作或鼠标移动所发送的码。例如,“左箭头键” 对应的是
ESC[D
,“Ctrl + 左箭头” 可能是ESC[1;5D
,而鼠标点击可能是像ESC[M:3
这样的内容。 - 输出码:程序可以输出这些代码来对文本上色、移动光标、清屏、隐藏光标、将文本复制到剪贴板、启用鼠标点击报告、设置窗口标题等。
现在我们来聊聊标准吧!
ECMA-48
我找到的与转义码相关的首个标准是 ECMA-48,它最初发布于 1976 年。
ECMA-48 做了两件事:
-
为转义码定义了一些通用格式(比如 “CSI” 码,格式是
ESC[
+ 某些内容,以及 “OSC” 码,格式是ESC]
+ 某些内容)。 -
定义了一些特定的转义码,例如 “将光标向左移动” 是
ESC[D
,“将文本变为红色” 是ESC[31m
。在该规范中,“光标左移” 的这个码被称为CURSOR LEFT
,而用于更改颜色的被叫做SELECT GRAPHIC RENDITION
。
这些格式是可扩展的,所以未来其他人有空间去定义更多转义码。如今很多流行的转义码在 ECMA-48 中并未定义,例如,终端应用程序(如 vim、htop 或 tmux)支持使用鼠标是很常见的,但 ECMA-48 没有为鼠标操作定义转义码。
xterm 控制序列
有很多转义码在 ECMA-48 中未做定义,例如: 启用鼠标点击报告(你在终端的哪里点击了?) 带括号粘贴(你输入的文本是粘贴的还是自己敲的?) OSC 52(终端应用程序可以用它将文本复制到系统剪贴板) 我认为(如果我说错了请纠正我!),上述这些以及其他一些转义码源自 xterm,在《XTerm 控制序列》中有记载,并且已被其他终端模拟器广泛采用。 这份 “xterm 所支持内容” 的清单严格来说不算一个标准,但 xterm 极具影响力,所以它似乎是一份重要的文档。
terminfo
在 20 世纪 80 年代(在某种程度上现在也是,但据我理解,在 80 年代这种情况要严重得多),终端实际支持的转义码存在大量差异。
为了解决这个问题,有一个针对各种终端转义码的数据库,叫做 “terminfo”。
看起来 terminfo 的标准被称为 X/Open Curses,不过由于某些原因,你需要创建一个账户才能查看该标准。它定义了数据库格式,以及用于访问该数据库的 C 库接口(“curses”)。
例如,你可以运行以下 bash 代码片段,来查看系统所知的所有不同终端针对 “清屏” 这一操作的所有可能转义码:
bash
for term in $(toe -a | awk '{print $1}')
do
echo $term
infocmp -1 -T "$term" 2>/dev/null | grep 'clear=' | sed's/clear=//g;s/,//g'
done
在我的系统上(可能在我使用过的每个系统上都是如此?),terminfo 数据库是由 ncurses 管理的。
程序应该使用 terminfo 吗?
我觉得有意思的是,应用程序在处理 ANSI 转义码时有两种主要方式:
-
根据 TERM 环境变量的内容,使用 terminfo 数据库来确定要使用哪些转义码。例如 fish 就是这么做的。
-
确定一组 “单一通用的” 转义码,这些码在 “足够多” 的终端模拟器中都能生效,然后直接将它们硬编码进去。
采用第二种方式(“不使用 terminfo”)的程序 / 库的一些例子包括:
我很好奇为什么人们可能会不再使用 terminfo,然后我发现了一篇由 fish 的一位维护者所写的关于 terminfo 的非常有趣且极为详细的吐槽文章,其观点是:
terminfo 的作者们 做了大量在当时极其重要且有帮助的工作。
但我的观点是,如今它不再那么重要了。” 我无法公正地概括这篇文章内容,所以就不总结了,我觉得它值得一读。
是否存在 “单一通用的一组” 转义码?
我刚才谈到了可以使用一组 “通用的” 转义码,让它们能在大多数人使用的环境下起作用这个想法。但这组转义码是什么呢?是否有共识呢?
我其实完全不知道这个问题的答案,但通过阅读相关资料,似乎它是以下内容的某种组合:
- VT100 所支持的代码(尽管其中一些在现代终端上已不相关)。
- ECMA-48 中的内容(我觉得这里面也有些东西现在不再相关了)。
- xterm 所支持的内容(不过我猜其中并非所有内容都真正得到了广泛支持)。
并且也许最终是 “确定你认为用户最常使用的终端模拟器,然后在这些模拟器上进行测试”,就如同网页开发者决定可以使用哪些 CSS 功能时所做的那样。
不过我认为对于终端而言,并不存在像 Can I use…? 或者 Baseline 这样的资源。(理论上 terminfo 应该是终端领域的 “caniuse”,但似乎新的终端功能被发明出来后,往往要花上十多年时间才会被加入到 terminfo 中,这让它的作用非常有限)。
使用 terminfo 的一些原因
我还在 Mastodon 上询问了为什么在 2025 年人们仍觉得 terminfo 有价值,得到了几个让我觉得有道理的原因:
-
有些人期望能够使用 TERM 环境变量来控制程序的行为(例如设置 TERM=dumb 时),而且在没有 terminfo 的情况下,对于这应该如何实现是没有标准的。
-
尽管现在终端模拟器之间的差异比 80 年代小了,但远非没有差异:有图形化终端、Linux 帧缓冲控制台、通过串行控制台连接到服务器时的情况、Emacs shell 模式,可能还有更多我没提到的情况。
-
并不存在一个关于 “单一通用的一组” 转义码的标准,而且有时程序使用的转义码实际上并没有得到足够广泛的支持。
terminfo 与用户代理检测
ncurses 使用 TERM
环境变量来决定使用哪些转义码的这种方式,让我想起了过去 Web 服务器有时会根据浏览器用户代理来决定提供哪个版本的网站。
这似乎也产生了一些相同的结果 ——iTerm2 将自己报告为 “xterm - 256color” 的方式,类似于 Safari 的用户代理是 “Mozilla/5.0 (Macintosh; Intel Mac OS X 14_7_4) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.3 Safari/605.1.15”。在这两种情况下,终端模拟器 / 浏览器最终都更改了自己的用户代理,以避开效果不佳的用户代理检测。
在 Web 领域,我们最终认定用户代理检测不是一种好做法,而是应该专注于标准化,这样我们就能向所有浏览器提供相同的 HTML/CSS。不过我不知道终端领域未来是否也会采取相同的方法 —— 我认为如今的终端生态比 Web 曾经的状况要更加碎片化,而且资金支持也少得多。
更多文档 / 标准
- Linux console_codes 手册页记录了 Linux 所支持的转义码。
- VT100 如何处理转义码和控制序列。
- kitty 键盘协议。
- 用于终端中链接的 OSC 8(以及关于其采用情况的说明)。
- tmux 对 ANSI 标准的总结。
- iTerm 的这份终端功能报告规范。
- Sixel 图形。
我认为这很有趣的原因
我有时会看到有人说 Unix 终端 “过时了”,而因为我非常喜欢终端,所以我总是很好奇哪些渐进式的改变可以让它看起来不那么 “过时”。
也许如果我们有一个更清晰的标准环境(就像 Web 领域那样!),终端模拟器开发者开发新功能就会更容易,终端应用程序的开发者也能更有信心地采用这些功能,这样我们都能从中受益,并且在终端中有更丰富的体验。
显然,对 ANSI 转义码进行标准化并非易事(ECMA-48 首次发布差不多是 50 年前了,可我们至今都还没完全实现标准化!)。我甚至都不知道所有的挑战是什么。但 HTML/CSS/JS 曾经的情况也极其糟糕,而现在已经好多了,所以也许还是有希望的。