在產生 ANSI 顏色代碼時,大多數錯誤來自於將跳脫序列視為不透明字串,而非具有明確文法的結構化控制訊息。由 ECMA-48 定義並由 xterm 擴充的 SGR (Select Graphic Rendition) 文法是嚴格的:控制序列開頭必須是實際的 ESC 位元組,後接一個左方括號,參數必須是以分號分隔的十進位整數,且序列必須以字母 m 作為結尾。上述任何一個部分出錯,或忘記附加結尾的 ESC[0m reset,終端機將會默默地錯誤地呈現您的文字、將粗體樣式洩漏到之後的每一行,或完全拒絕解譯參數。由於跳脫位元組屬於控制資料,一個格式錯誤的序列若被貼到記錄檔或聊天訊息中,可能會隱藏文字、偽造視覺上的行,或為任何捲動經過的人中斷互動式終端機狀態。上述每一種失敗模式,都可以透過一個會驗證參數、提供可見表示法、並自動附加 reset 的產生器來預防。

how do i avoid mistakes when i generate ansi color codes when using ansi escape codes
how do i avoid mistakes when i generate ansi color codes when using ansi escape codes

為什麼產生 ANSI 顏色代碼會出錯

SGR 序列表面上看起來很簡單,但字串中的每一個片段都有特定的作用。ESC 位元組代表控制序列的開始,而不是可列印的文字。跳脫字元之後的左方括號,是依據 ECMA-48 規格用來標記為 CSI (Control Sequence Introducer)的記號。十進位數字是用來選擇呈現參數,而非任意的樣式。字母 m 是用來告知終端機這是 Select Graphic Rendition 指令的結尾字元,有別於游標移動、清除螢幕或其他 CSI 系列。漏掉其中任何一個,終端機就會回退到最寬容的解讀方式,通常是「完全忽略該序列,並以無顏色的方式列印我的文字」。當開發人員確實看到某些東西被呈現出來時,下一個失敗模式就是樣式洩漏:之後列印的每一行都會繼承最後一個 SGR 狀態,因為沒有附加 ESC[0m reset。

另一個常見的錯誤,是將顏色編號視為 RGB 值。SGR 代碼 30 到 37 以及 40 到 47 是調色盤位置索引,由 ECMA-48 定義,用於依序選取八種基本顏色:黑、紅、綠、黃、藍、洋紅、青、白。xterm 相容的明亮延伸將 90 到 97 以及 100 到 107 對應到相同順序的相同顏色名稱。實際呈現的 RGB 是由終端機模擬器、使用中的佈景主題、使用者的色彩設定檔,以及當下的輔助設定所決定。代碼 31 並不代表特定的紅色陰影;它代表的是終端機目前稱為紅色的位置。

第三類錯誤在產生時看不出來,但在貼上時卻非常明顯。原始的跳脫位元組是控制資料。當它們出現在記錄檔、聊天訊息、程式碼審查或 CI 產出物中時,任何開啟它們的終端機或分頁器都可能會解譯這些資料。這可能會隱藏字元、繪製看似錯誤的誤導性紅色區塊,或破壞互動式工作階段的捲動區域。這個錯誤對作者來說是隱形的,卻由讀者來承擔。

正確 SGR 序列的組成結構

一個可運作的 SGR 序列依照順序恰好有六個部分:

  1. ESC 位元組,即單一控制字元 0x1B。在原始碼中,寫成跳脫序列 "\x1b" 或 "\u001b",絕不是四個字元 backslash、x、one、b。
  2. 左方括號:[。
  3. 一個或多個以分號分隔的十進位整數。每個整數都是一個參數。
  4. 結尾字母:m。
  5. 要呈現的文字。
  6. 一個結尾的 reset,同樣以 ESC 開頭,然後是 [0m。

參數的標準順序,是將樣式代碼放在顏色代碼之前。粗體是參數 1,底線是參數 4,它們會與前景與背景顏色代碼組合。前景顏色使用 30 到 37 作為基本調色盤,90 到 97 作為明亮調色盤。背景顏色使用 40 到 47 作為基本調色盤,100 到 107 作為明亮調色盤。若要產生一個粗體加底線、明亮紅色前景與藍色背景的序列,參數為 1、4、91、44,寫成 1;4;91;44。若以 \x1b 表示前導 ESC,完整字串為 \x1b[1;4;91;44mhello\x1b[0m,而這個可見的跳脫表示法,正是讓您能在不觸發周圍頁面色變化的情況下檢視該序列的關鍵。

由 ECMA-48 定義並由 xterm 擴充的完整十六色 SGR 對照表如下:

位置前景 (基本)前景 (明亮)背景 (基本)背景 (明亮)
Black309040100
Red319141101
Green329242102
Yellow339343103
Blue349444104
Magenta359545105
Cyan369646106
White379747107

超出此對照表的編號,包括用於非調色盤顏色選取的 38 與 48,以及它們在 xterm 中用於 256 色與真實色的延伸,雖然屬於更廣義 SGR 規格的一部分,但會被產生器的驗證機制明確拒絕。堅持使用這 16 個位置,可以消除一整類錯誤——也就是當某個消費者端沒有實作這些延伸時,將原始參數當作可見文字而非顏色來列印的情況。

在不犯錯的情況下建立序列

避免產生錯誤的最快方式,是將組裝工作委派給一個會驗證每個部分的產生器。ANSI 顏色代碼產生器遵循以下工作流程:

  1. 在文字欄位中輸入您想要套用樣式的精確文字。純 ASCII 是最安全的,並請記住,該文字是嵌入在控制序列內部並與其一同傳遞的。
  2. 從 16 個調色盤位置中選擇一個選用的前景顏色。如果不需要覆寫前景,可以跳過此欄位。
  3. 從相同的 16 個位置中選擇一個選用的背景顏色。背景與前景可以獨立設定。
  4. 切換粗體與底線的開關。粗體對應參數 1,底線對應參數 4;兩者互相獨立,可以組合使用。
  5. 閱讀可見的跳脫表示法。跳脫位元組會以 \x1b 顯示,因此該序列在瀏器中會以可列印文字的形式呈現,您可以在不更動頁面顏色的情況下檢視它。
  6. 檢視列於可見字串旁邊的 SGR 參數。它們會依標準順序、以分號分隔顯示,且只包含來自十六色對照表的數字,以及樣式代碼 1 與 4。任何不支援的組合都會在序列建立之前被拒絕。
  7. 點擊 Copy,將原始控制位元組複製到剪貼簿。該按鈕複製的是實際的 ESC 位元組加上括號、參數、結尾字元、您的文字,以及結尾的 ESC[0m reset,而不是四個字元的 \x1b。
  8. 僅將這些位元組貼到會解它們的環境中:終端機多工器的工作窗格、即將由具備終端機感知能力的程式執行的原始檔,或除錯工作階段中的測試固定資料。避免將它們以原始位元組的形式貼到記錄檔、議題追蹤系統或聊天訊息中。

如果可見的表示法顯示 \x1b[1;4;91;44m,後面接著您的文字,並以 \x1b[0m 作為結尾,那麼這個結構就是正確的。剩下的問題就只在於目的終端機,而不在於序列本身。

破壞記錄與安全性的錯誤

一個在您終端機中運作完美的控制序列,一旦落到不屬於它的地方,就會變成一個小小的攻擊面。已證實含有不受信任之控制位元組的記錄,可以隱藏文字、偽造會捲過真正訊息的視覺行、產生誤導性的可點擊連結,並透過切換字元集或重設螢幕來亂終端機狀態。這裡的錯誤有兩層:將原始位元組輸出到記錄中,以及未先進行淨化就將不受信任的使用者輸入透過終端機回傳。

正確的紀律是維持兩條路。第一條是具備樣式、具備終端機感知能力的路徑,在此路徑中使用產生器的原始位元組是合適的。第二條則是用於記錄檔、議題、CI 產出物與聊天的純文字路,在該路徑中,每個控制位元組要嘛被移除,要嘛被明確地跳脫為 \x1b。大多數記錄函式庫之所以接受非顏色格式化器,正是為了這個原因,而 ECMA-48 規格也將 ESC 位元組視為控制序列的開頭,而不是可列印的內容。請以相同方式對待任何不受信任的文字:絕不讓使用者輸入在未經過的情況下流入具樣式的路徑,也絕不讓您自己具樣式的輸出在未移除或跳脫控制位元組的情況下流入純文字路。

一個相關的錯誤,是假設產生器的可見跳脫表示法可以安全地貼到任何地方。事實並非如此。可見表示法會將跳脫位元組寫成四個字元 \x1b,因此覽器或文字編輯器會將它們原樣顯示。然而,具備終端機感知能力的消費者端會看到底層的 ESC 位元組並解譯整個序列。Copy 按刻意複製的是原始位元組,而可見表示法則僅供檢視使用。在文件、測試與工單中請使用可見表示法,並將原始位元組保留給會實際呈現它們的環境。

取決於終端機而非程式碼的錯誤

一個完全有效的 SGR 序列,仍然可能會呈現錯誤,而這類錯誤通常來自於假設某一個終端機能代表所有終端機。粗體就是典型的例子:在許多終端機中,參數 1 選擇的是強度提高的顏色,而不是更粗的字體重量;而在某些較舊的終端機中,它會將前景切換到明亮調色盤。如果您的程式碼預期要同時在現代圖形終端機與透過 SSH 的 TTY 工作階段中呈現,請以最低共同標準來設計,並在兩種環境下實際驗證呈現結果。

顏色名稱描述的是調色盤位置,而非保證的 RGB 值。同樣的代碼 91,即明亮紅色前景,在 Solarized Light 佈景主題中可能會呈現為鮮的番茄紅,在 Solarized Dark 佈景主題中則可能呈現為去飽和的磚紅色,這個值還可能進一步被輔助設定、多工器處理與遠端環境變數重新對應。NO_COLOR 慣例受到大多數現代 CLI 遵守,要求程式完全不輸出任何顏色代碼;而 Windows 上終端機的虛擬終端機處理模式,則決定了 SGR 序列是否會被解譯。這些在產生的位元組中完全看不到,只會在呈現的輸出中顯現出來。請在使用者實際運行的環境中進行測試,並提供一個有文件記載的方式,可在 NO_COLOR 與非互動的情況下停用顏色。

同樣的注意事項也適用於底線。參數 4 會啟用底線,但線條的形狀——直線、波浪線、雙線或點線——以及底線顏色,都是由終端機決定,而非由您的程式碼決定。如果底線在語意上很重要,例如標示連結、刪除的行或警告,請勿依賴形狀或顏色來傳達。請搭配使用文字與樣式,並在底線可能不可見或被重新利用的螢幕讀器或輔助過濾器中進行測試。

Verifying the Sequence Before You Ship It

Three checks catch the majority of mistakes before they reach a user. First, the structural check: confirm the raw bytes on the clipboard start with the ESC byte, followed by [, then a string of decimal digits and semicolons, then m, then the text, then ESC again, then [0m. The visible escaped representation in the generator is the easiest way to do this without exposing your terminal to a malformed sequence. Second, the semantic check: confirm the parameter list is the smallest necessary set, that style codes come before color codes, and that every color code is in the 16-color table. Third, the destination check: paste the raw sequence into the same terminal emulator, theme, and output mode your users will run, and confirm the reset actually returns later output to the default rendition.

The generator handles the structural and semantic checks on your behalf by validating the input, rejecting unsupported style and color numbers, ordering the parameters canonically, and appending a reset every time a prefix is created. The destination check is yours. The reset is a guardrail, not a guarantee, and the browser has no way to know whether the destination supports ECMA-48, xterm extensions, Windows virtual terminal processing, NO_COLOR conventions, or redirected noninteractive output. Treat the generator as a way to remove the assembly mistakes, then verify behavior in the real terminal that will actually render your output.

For a deeper look, see ASCII Chart Cheat Sheet: All 128 Codes in 4 Notations.

For a deeper look, see How to Share Spotify Code as a PNG Image.