Standards for ANSI escape codes

Julia Evans

ANSI 转义码的标准

你好!今天我想聊聊 ANSI escape codes(ANSI 转义码)。

很长一段时间里,我对 ANSI escape codes 只有一个模糊的认识(“就是在终端里把文字变红之类的东西”),但完全不知道它们到底应该在哪里定义、是否有相关标准。我只是隐约觉得它们周围“暗藏玄机”。今年在学习终端的过程中,我了解到:

  1. ANSI escape codes 为终端带来了许多可用性提升(你知道吗,即使通过 SSH 连接到远程机器,也有办法复制到本地系统剪贴板??靠的就是一个叫 OSC 52 的转义码!)
  2. 它们并没有完全标准化,因此并不总是能可靠运行。而且由于它们是不可见的,排查转义码问题会让人极其沮丧。

所以我想为自己整理一份关于转义码现有标准的清单,因为我想知道它们是否注定让人觉得不可靠、令人沮丧,或者未来我们是否能更有信心地依赖它们。

什么是转义码?

你有没有在终端里按过左方向键,然后看到 ^[[D?那就是一个转义码!它之所以叫“转义码”,是因为第一个字符是“escape”字符,通常写作 ESC\x1b\E\033^[

转义码是你的终端模拟器与运行在终端中的程序交换各种信息(颜色、鼠标移动等)的方式。转义码有两种:

  1. 输入码(input codes),由终端模拟器在按键或鼠标移动无法用 Unicode 表示时发送。例如“左方向键”是 ESC[D,“Ctrl+左方向键”可能是 ESC[1;5D,而点击鼠标可能是类似 ESC[M :3 的内容。
  2. 输出码(output codes),程序可以通过打印它们来实现给文字上色、移动光标、清屏、隐藏光标、复制文本到剪贴板、启用鼠标报告、设置窗口标题等。

现在我们来聊聊标准!

ECMA-48

我找到的第一个与转义码相关的标准是 ECMA-48,它最初发布于 1976 年。

ECMA-48 做了两件事:

  1. 定义了一些转义码的通用格式(比如“CSI”码,即 ESC[ 加上一些内容,以及“OSC”码,即 ESC] 加上一些内容)
  2. 定义了一些具体的转义码,比如“将光标向左移动”是 ESC[D,或“将文字变红”是 ESC[31m。在规范中,“向左移动光标”的那个被称为 CURSOR LEFT,而用于改变颜色的那个被称为 SELECT GRAPHIC RENDITION

这些格式是可扩展的,因此为未来其他人定义更多转义码留出了空间。如今许多流行的转义码并未在 ECMA-48 中定义:例如,终端应用(如 vim、htop 或 tmux)支持鼠标操作已经相当普遍,但 ECMA-48 并没有为鼠标定义转义码。

xterm 控制序列

有许多转义码并未在 ECMA-48 中定义,例如:

  • 启用鼠标报告(你在终端的哪里点击了?)
  • 括号粘贴(bracketed paste)(那段文字是你粘贴的还是输入的?)
  • OSC 52(终端应用可用它将文本复制到系统剪贴板)

我认为(如果我说错了请指正!)这些以及其他一些转义码都来自 xterm,记录在 XTerm Control Sequences 中,并已被其他终端模拟器广泛实现。

这份“xterm 支持什么”的清单严格来说算不上标准,但 xterm 的影响力极大,因此它似乎是一份重要的文档。

terminfo

在 80 年代(某种程度上今天也是如此,但据我了解,80 年代的情况要严重得多),不同终端实际支持的转义码差异巨大。

为了解决这个问题,出现了一个名为“terminfo”的数据库,其中收录了各种终端的转义码。

terminfo 的标准似乎叫做 X/Open Curses,不过出于某种原因,你需要创建一个账号才能查看该标准。它定义了数据库格式以及用于访问该数据库的 C 语言库接口(“curses”)。

例如,你可以运行下面这段 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 escape codes 时主要有两种做法:

  1. 根据 TERM 环境变量中的内容,使用 terminfo 数据库来确定该使用哪些转义码。例如 Fish 就是这么做的。
  2. 找出能在“足够多”终端模拟器上工作的“单一通用集合”,然后直接硬编码这些转义码。

采取第 2 种做法(“不使用 terminfo”)的程序/库的一些例子包括:

我很好奇人们为什么会逐渐远离 terminfo,于是找到了这篇来自某位 fish 维护者的非常有趣且极其详细的关于 terminfo 的长篇抱怨,其中主张:

[terminfo 的作者们]做了大量工作,在当时极其重要且富有帮助。我的观点是,现在已不再如此。

我无法很好地概括它,所以就不总结了,我觉得值得一读。

是否存在“单一通用”的转义码集合?

我刚刚谈到可以使用一个对大多数人都有效的转义码“通用集合”的想法。但这个集合到底是什么?是否存在共识?

我真的完全不知道答案,但通过阅读一些资料来看,它似乎是以下几项的某种组合:

  • VT100 所支持的那些编码(尽管有些在现代终端上已不再相关)
  • ECMA-48 中的内容(我认为其中也有些已不再相关)
  • xterm 所支持的内容(不过我猜并非其中的所有内容都得到了足够广泛的支持)

而最终也许是“找出你认为用户最常使用的那些终端模拟器并在其中进行测试”,就像网页开发者在决定哪些 CSS 特性可以放心使用时所做的那样

不过我认为终端领域并没有像 Can I use…?Baseline 这样的资源。(理论上 terminfo 本应是终端的“caniuse”,但当人们发明新的终端功能时,往往需要 10 多年才能被加入其中,这使得它的作用非常有限)

使用 terminfo 的一些理由

我也在 Mastodon 上询问了人们为什么在 2025 年仍觉得 terminfo 有价值,并得到了几个让我觉得有道理的理由:

  • 有些人期望能够使用 TERM 环境变量来控制程序的行为(例如使用 TERM=dumb),而在后 terminfo 时代,对于这该如何运作并没有标准
  • 尽管终端模拟器之间的差异比 80 年代了,但远未消除:有图形化终端、Linux 帧缓冲控制台、通过串口控制台连接服务器时的环境、Emacs shell 模式,可能还有更多我遗漏的情况
  • 对于“单一通用”转义码集合究竟是什么,并没有统一的标准,有时程序使用的转义码实际上并没有得到足够广泛的支持

terminfo 与用户代理检测

ncurses 使用 TERM 环境变量来决定使用哪些转义码的方式,让我想起网络服务器过去有时会利用浏览器用户代理(user agent)来决定提供哪个版本的网站。

它似乎也带来了同样的结果——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”。在这两种情况下,终端模拟器/浏览器最终都通过改变自己的用户代理来绕过效果不佳的用户代理检测。

在网络上,我们最终认定用户代理检测不是一种好的做法,转而专注于标准化,以便向所有浏览器提供相同的 HTML/CSS。我不知道同样的做法是否是终端的未来——我认为如今终端领域的碎片化程度远超网络曾经的情况,而且资金支持也少得多。

更多相关文档/标准

为什么我觉得这很有趣

我有时会看到有人说 unix 终端已经“过时”了,而正因为我非常喜欢终端,我总是很好奇哪些渐进式改进能让它不那么“过时”。

也许如果我们拥有更清晰的标准版图(就像我们在网络上拥有的那样!),终端模拟器开发者就能更容易地构建新功能,终端应用的作者也能更自信地采用这些功能,从而让我们所有人都能从中受益,并在终端中获得更丰富的体验。

显然,标准化 ANSI escape codes 并非易事(ECMA-48 首次发布至今已近 50 年,而我们仍未实现!)。我甚至不清楚所有的挑战是什么。但 HTML/CSS/JS 的情况过去也曾极其糟糕,而现在已经好了得多,所以也许还是有希望的。

原文由 Julia Evans 发布

本文章由 muse-spark-1.2-contributor 进行翻译