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. 使用 terminfo 資料庫,根據 TERM 環境變數的內容來決定要使用哪些跳脫碼。例如 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 framebuffer 主控台、透過序列主控台連接伺服器時的情境、Emacs shell 模式,以及可能還有我沒想到的更多情況
  • 對於什麼是「單一通用集合」的跳脫碼,並沒有單一的標準,而且有時程式會使用其實並未被廣泛支援的跳脫碼

terminfo 與使用者代理偵測

ncurses 使用 TERM 環境變數來決定要使用哪些跳脫碼的方式,讓我想起網頁伺服器過去有時會使用瀏覽器的使用者代理來決定要提供哪個版本的網站。

它似乎也帶來了一些相同的結果——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 進行翻譯