ANSI 跳脫序列的標準
原文由 Julia Evans 于 發布,訂閱此部落格
哈囉!今天我想來聊聊 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 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 跳脫序列時,主要有兩種做法:
- 根據
TERM環境變數的內容,利用 terminfo 資料庫來決定該用哪些跳脫序列。例如 Fish 就是這樣做的。 - 找出在「夠多」終端機模擬器上都能通用的「一套共通」跳脫序列,然後直接寫死在程式裡。
採取第二種做法(「不用 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 mode,大概還有更多我沒想到的
- 對於什麼才是「一套共通」的跳脫序列,並沒有統一的標準,有時候程式會用到其實並沒有被廣泛支援的跳脫序列
terminfo 與使用者代理偵測
ncurses 利用 TERM 環境變數來決定該用哪些跳脫序列的方式,讓我想起以前網頁伺服器有時會用瀏覽器的 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 來繞過運作不佳的使用者代理偵測。
在網頁的世界,我們最後認定使用者代理偵測不是好做法,轉而專注於標準化,這樣才能對所有瀏覽器提供相同的 HTML/CSS。不過我不確定同樣的做法在終端機領域是否也是未來——我覺得如今終端機的生態比起網頁曾經的狀況要零碎得多,而且投入的資源也少得多。
更多相關文件/標準
更多與跳脫序列相關的文件和標準,不分先後:
- Linux console_codes 說明文件記載了 Linux 支援的跳脫序列
- VT 100 如何處理跳脫序列與控制序列
- kitty 鍵盤協定
- 用於終端機內超連結的 OSC 8(以及關於 採用情況的筆記)
- 來自 tmux 的 ANSI 標準摘要
- 這個來自 iTerm 的 終端機功能回報規格
- sixel 圖形
為什麼我覺得這很有趣
我有時會看到有人說 unix 終端機已經「過時」了,而因為我非常喜歡終端機,我總是很好奇有哪些漸進式的改變可以讓它感覺不那麼「過時」。
也許如果我們有更清晰的標準版圖(就像我們在網頁領域擁有的那樣!),終端機模擬器的開發者就能更容易打造新功能,而終端機應用程式的作者也能更有信心地採用這些功能,讓我們所有人都能從中受益,並在終端機中獲得更豐富的體驗。
顯然,要把 ANSI 跳脫序列標準化並不容易(ECMA-48 首次發表至今都快 50 年了,我們卻還沒做到!)。我甚至不知道所有的挑戰到底有哪些。但 HTML/CSS/JS 的狀況過去也曾經糟得一塌糊塗,現在卻已經好得多了,所以也許還是有希望的。
隨機一篇部落格
留言
登入後參與討論