Standards for ANSI escape codes

Julia Evans

ANSI 转义码标准

原文由 Julia Evans 发布,订阅该博客

你好!今天我想聊聊 ANSI 转义码。

很长一段时间里,我对 ANSI 转义码只有模糊的认识(“就是在终端里把文字变红之类的东西”),根本不清楚它们到底应该在哪里定义、有没有相关标准。我只是隐隐觉得,这里面“暗藏玄机”。今年在学习终端的过程中,我了解到:

  1. ANSI 转义码为终端的许多易用性改进提供了支持(你知道在 SSH 到远程机器时还能直接复制到本地系统剪贴板吗??这靠的就是一个叫 OSC 52 的转义码!)
  2. 它们并没有完全标准化,因此并不总是能可靠地工作。而且由于它们是不可见的,排查转义码问题会让人非常头疼。

所以我想为自己整理一份关于转义码现有标准的清单,因为我想弄清楚:它们是否注定让人觉得不可靠、让人沮丧,还是说未来有一天,我们可以更放心地依赖它们。

什么是转义码?

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

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

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

接下来我们来聊聊标准!

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 中定义,例如:

  • 启用鼠标报告(你在终端的哪里点了鼠标?)
  • 括号粘贴(这段文字是粘贴进来的还是手打的?)
  • 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 转义码时主要有两种做法:

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

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

我很好奇人们为什么会逐渐弃用 terminfo,于是找到了一位 fish 维护者撰写的、非常有趣且极其详尽的关于 terminfo 的长文吐槽,其中说道:

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

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

是否存在一套“通用”的转义码?

我刚才提到了可以使用一套对大多数人都有效的“通用”转义码的想法。但这套集合到底是什么?对此有共识吗?

我对此完全没有答案,但在阅读了一些资料后,看起来它是以下几者的组合:

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

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

不过我认为终端领域并没有像 Can I use…?Baseline 那样的资源。(理论上 terminfo 应该是终端的“caniuse”,但似乎每当有人发明新的终端功能,往往要等 10 年以上才会被收录进去,这让它的作用非常有限)

仍需使用 terminfo 的一些理由

我也在 Mastodon 上问了大家为什么在 2025 年仍觉得 terminfo 有价值,得到了几个我觉得很有道理的理由:

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

terminfo 与 User Agent 检测

ncurses 利用 TERM 环境变量来决定使用哪些转义码的方式,让我想起了过去 Web 服务器有时会利用浏览器 User Agent 来决定提供哪个版本的网页。

而且它似乎也带来了类似的结果——iTerm2 将自己报告为“xterm-256color”的做法,让人联想到 Safari 的 User Agent 是“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”。在这两种情况下,终端模拟器/浏览器最终都通过修改自己的 User Agent 来绕开效果不佳的 User Agent 检测。

在 Web 领域,我们最终认定 User Agent 检测不是一种好的实践,转而专注于标准化,以便向所有浏览器提供相同的 HTML/CSS。我不确定同样的做法是否也适用于终端的未来——我认为如今终端领域的碎片化程度远超过 Web 曾经的状况,而且投入的资金也要少得多。

更多相关文档/标准

还有一些与转义码相关的文档和标准,不分先后:

为什么我觉得这很有意思

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

也许如果我们能拥有更清晰的标准格局(就像 Web 领域那样!),终端模拟器开发者就能更轻松地构建新功能,终端应用的作者也能更自信地采用这些功能,从而让我们都能从中受益,在终端中获得更丰富的体验。

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

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

评论