终端转义序列与 OSC:把“乱码”看懂的那一刻
0. 故事背景
Coldrain 的 Hyprland 桌面环境使用了 illogical-impulse 的主题,配置文件自然也是使用人家编辑好的方案。
有一天,Coldrain 在微调配置文件的时候,偶然在 fish 的配置文件 ~/.config/fish/config.fish 中发现了这么一段内容:
if test -f ~/.local/state/quickshell/user/generated/terminal/sequences.txt
cat ~/.local/state/quickshell/user/generated/terminal/sequences.txt
end
为什么配置文件里要 cat 一个 .txt 文件呢?Coldrain 于是前去查看该文件的内容,使用 nvim ~/.local/state/quickshell/user/generated/terminal/sequences.txt 后,发现文件内部是一堆乱七八糟的代码
Coldrain 看不懂,于是默默退出,然后重新打开一个新的 tty 后(配置不同),尝试执行 cat ~/.local/state/quickshell/user/generated/terminal/sequences.txt 这一操作,突然间,新的 tty 背景变色了,透明度与高斯模糊都被更改了,好神奇!
Coldrain 继续往下追,发现了幕后黑手,就是“终端转义控制序列”(terminal escape sequences),而那份神秘的 sequences.txt,其实是一整段 OSC 控制脚本。
这篇 blog 就想把这件事从头到尾捋清楚:终端转义序列是什么,OSC 又是什么,两者是什么关系,以及它们是怎样在日常使用中悄悄出手的。
1. 终端不只是一个“黑框”
在类 Unix 系统里,我们习惯把屏幕上的黑框叫“终端”。但从程序的视角来看,终端更像是一个“会说点特殊暗号的文本设备”。
普通字符,比如 abc123,终端就老老实实按字形画出来。而一旦遇到某些特殊开头的字节,比如十六进制的 0x1b(ESC,转义符),终端就会切换成“指令模式”,把后面的内容当成控制命令来解析,而不是直接显示。
比如:
echo -e "\x1b[31mHello\x1b[0m"
这条命令输出的不是 \x1b[31m 这些字面文本,而是一段控制光标样式和前景色的“暗号”,终端接到之后,把后面的 “Hello” 涂成红色,然后再用 \x1b[0m 把样式重置。
这些以 ESC 开头的“暗号”整体,就叫作终端转义控制序列。
2. 终端转义控制序列:一个家族,而不是一个指令
“终端转义序列”不是某一个具体的指令,而是一个统称。只要满足两大条件,大都可以归类到这个家族里:
- 以 ESC 开头,ASCII 十进制 27,常写作
\033或\x1b - 后面跟一串有特殊语法的字符,来告诉终端“该干什么”
在经典的 ANSI / xterm 体系里,这个家族下面有好几个分支,常见的有:
- CSI(Control Sequence Introducer):通常写作
ESC [,也就是\x1b [,主要用来控制光标移动、清屏、设置属性(例如颜色、粗体等) - OSC(Operating System Command):写作
ESC ],也就是\x1b ],主要用来发给终端或宿主系统一些“命令”,比如改窗口标题、修改调色板颜色、操作剪贴板等等 - 还有 DCS、PM、APC 等不那么日常的类型,通常出现在一些高级终端特性或复杂协议中
所以,终端转义控制序列可以理解为一个“总称”,而 CSI / OSC 则是具体的语法分支。
❓ 关于 ANSI
ANSI 的全称是 American National Standards Institute(美国国家标准协会),本职工作是:制定和协调各种标准。
在我们聊终端的时候,说到 “ANSI” 通常有两层含义:
- 狭义:ANSI 参与制定过早期的“视频终端控制”相关标准(比如 ANSI X3.64),这些后来演变为你现在看到的那套 ESC 开头的控制序列体系(再往后被 ECMA、ISO 接着标准化)。
- 广义:大家习惯把“那套 ESC 开头的控制序列”叫做 ANSI 转义序列 / ANSI escape codes(比如:
\x1b[31m改成红色、\x1b[2J清屏、\x1b[H光标回到左上角)。还会说“这个终端支持 ANSI 颜色”“Windows 以前不支持 ANSI,后面才支持 VT/ANSI 。”
❓ 关于 xterm
xterm 是 X Window System(X11)下的一个经典终端模拟器程序,可以理解成:图形界面里面开的那个“终端窗口”之一。
它有两个重要的身份:
- 一个祖先级别,且现在仍然存在的终端模拟器:启动之后会给你一个终端窗口,在其中运行
bash/zsh等 shell 程序,同时可以实现解析程序输出的 ESC 控制序列- 大家都在借鉴的“事实标准”实现:很多现代终端(gnome-terminal、konsole、kitty、alacritty、wezterm、Windows Terminal 等)都会尽量兼容 “xterm 的行为”。在
$TERM里经常可以看到 xterm / xterm-kitty 之类的值,意思就是:“我大概长得像 xterm,请按 xterm 那套控制序列来对待我”。很多控制序列、尤其是 OSC(比如 OSC 4/10/11/52)的行为说明,现在查资料基本都是看 “xterm control sequences” 那份文档。比如现在查看你的
$TERM:echo $TERM如果你使用了上述的现代终端,那么你会发现输出值中大概率会有
xtem字眼
3. 关于 CSI
先来看一个小例子:
echo -e "\x1b[31mRed\x1b[0m"
echo -e "\x1b[2J\x1b[H"
这里的 \x1b[31m、\x1b[2J、\x1b[H 都是 CSI 控制序列,它们的共同特征是:
- 以
ESC [开头(\x1b[) - 后面跟一些数字参数和一个字母结尾
比如:
\x1b[31m:设置前景色为红色\x1b[2J:清屏\x1b[H:把光标移动到左上角(1 行 1 列)
很多人平时根本不知道 “CSI” 这个名字,但每天看到的彩色 ls 输出、粗体提示符、各种 TUI 程序(htop、vim)刷新屏幕,基本都靠这一类序列。
4. 关于 OSC
如果说 CSI 更偏“光标和样式控制”,OSC 就更像是“给终端本身下命令”的那一支。
OSC 的基本格式是:
ESC ] 参数号 ; 参数内容 BEL
# 或
ESC ] 参数号 ; 参数内容 ESC \
其中 BEL 是响铃(\x07),而 ESC \ 被用作字符串结束符(ST,String Terminator)。
在 xterm 及其兼容终端里,不同的参数号代表不同功能。几个常用的例子是:
OSC 0;title或OSC 2;title:设置终端窗口标题;OSC 4;index;#RRGGBB:设置调色板中某个颜色的 RGB 值;OSC 10;#RRGGBB:设置默认前景色;OSC 11;#RRGGBB:设置默认背景色;OSC 52;...:用来和剪贴板交互(复制/粘贴)。
5. 回顾神秘脚本
当 Coldrain 在 config.fish 里 cat 这个文件时,这些控制序列会被终端解释,从而动态修改终端配色(包括前景/背景/调色板),所以背景颜色会变。
既然上面直接使用 nvim 无法直观看出该序列的内容,Coldrain 查阅资料后,在使用 nvim 打开该文件后,使用 %!xxd 指令,文件的内容就变得整齐多了:
xxd这个命令的作用是将文件(或标准输入)变成十六进制“十六进制转储(hex dump)”,或者反过来,把十六进制还原成原始二进制
00000000: 1b5d 343b 303b 2331 4131 4132 351b 5c1b .]4;0;#1A1A25.\.
00000010: 5d31 3b30 3b23 3141 3141 3235 1b5c 1b5d ]1;0;#1A1A25.\.]
00000020: 343b 313b 2344 3935 3446 461b 5c1b 5d34 4;1;#D954FF.\.]4
00000030: 3b32 3b23 3634 4443 4630 1b5c 1b5d 343b ;2;#64DCF0.\.]4;
00000040: 333b 2346 4644 4345 451b 5c1b 5d34 3b34 3;#FFDCEE.\.]4;4
00000050: 3b23 3841 4144 4434 1b5c 1b5d 343b 353b ;#8AADD4.\.]4;5;
00000060: 2343 3139 4245 341b 5c1b 5d34 3b36 3b23 #C19BE4.\.]4;6;#
00000070: 3843 4432 4633 1b5c 1b5d 343b 373b 2345 8CD2F3.\.]4;7;#E
00000080: 3944 3344 431b 5c1b 5d34 3b38 3b23 4336 9D3DC.\.]4;8;#C6
00000090: 4234 4244 1b5c 1b5d 343b 393b 2346 3141 B4BD.\.]4;9;#F1A
000000a0: 3546 461b 5c1b 5d34 3b31 303b 2346 3746 5FF.\.]4;10;#F7F
000000b0: 4446 461b 5c1b 5d34 3b31 313b 2346 4646 DFF.\.]4;11;#FFF
000000c0: 4646 461b 5c1b 5d34 3b31 323b 2343 3644 FFF.\.]4;12;#C6D
000000d0: 4646 341b 5c1b 5d34 3b31 333b 2345 4543 FF4.\.]4;13;#EEC
000000e0: 4646 461b 5c1b 5d34 3b31 343b 2346 3646 FFF.\.]4;14;#F6F
000000f0: 4246 461b 5c1b 5d34 3b31 353b 2345 3045 BFF.\.]4;15;#E0E
00000100: 3046 461b 5c1b 5d31 303b 2345 3944 3344 0FF.\.]10;#E9D3D
00000110: 431b 5c1b 5d31 313b 5b31 3030 5d23 3141 C.\.]11;[100]#1A
00000120: 3141 3235 1b5c 1b5d 3132 3b23 4539 4433 1A25.\.]12;#E9D3
00000130: 4443 1b5c 1b5d 3133 3b23 4539 4433 4443 DC.\.]13;#E9D3DC
00000140: 1b5c 1b5d 3137 3b23 4539 4433 4443 1b5c .\.]17;#E9D3DC.\
00000150: 1b5d 3139 3b23 3141 3141 3235 1b5c 1b5d .]19;#1A1A25.\.]
00000160: 343b 3233 323b 2345 3944 3344 431b 5c1b 4;232;#E9D3DC.\.
00000170: 5d34 3b32 3536 3b23 4539 4433 4443 1b5c ]4;256;#E9D3DC.\
00000180: 1b5d 3730 383b 5b31 3030 5d23 3141 3141 .]708;[100]#1A1A
00000190: 3235 1b5c 1b5d 3131 3b23 3141 3141 3235 25.\.]11;#1A1A25
000001a0: 1b5c 0a
那么,这一大坨十六进制到底在说些什么呢?
5.1 从第一行看起
首先我们来看第一行:
00000000: 1b5d 343b 303b 2331 4131 4132 351b 5c1b .]4;0;#1A1A25.\.
我们直接查表按字节还原成字符就是:
ESC ] 4 ; 0 ; #1A1A25 ESC \
其中:
1b=ESC(转义字符)5d=]- 后面是可见字符:
4;0;#1A1A25 1b 5c=ESC \,这是字符串结束符 ST(String Terminator),xterm/kitty 风格的结尾
这一整串叫做 OSC (Operating System Command)控制序列,格式一般是:
ESC ] 参数号 ; 参数内容 ST
这一条的完整含义是:OSC 4 ; 0 ; #1A1A25,即把调色板中颜色 0 设置为 #1A1A25(一个很深的背景色)
5.2 整段序列的含义
根据文件的内容,这一大坨序列的模式都和其第一行的模式类似,也即是一整组的:
ESC ] 4;0;#... ST
ESC ] 4;1;#... ST
ESC ] 4;2;#... ST
...
ESC ] 4;15;#... ST
...
ESC ] 12;#... ST
其含义为:
OSC 4代表设置调色板颜色4;N;#RRGGBB代表把第 N 号颜色设置为#RRGGBBOSC 10;#RRGGBB:设置前景色(文字颜色)OSC 11;#RRGGBB:设置背景色OSC 12;#RRGGBB:设置光标颜色OSC 13;#RRGGBB:设置光标文本颜色OSC 17, 19:设置选中高亮区域相关颜色OSC 4;232;#...:扩展 256 色调色板中高编号颜色
6. 写在最后
终端转义控制序列看上去像是“冷门知识”,但只要你开始折腾主题、写点 TUI 工具、调试奇怪的日志文件,迟早会撞到它们。CSI 决定了屏幕上光标怎么跳、颜色怎么变,OSC 决定了终端整体长什么样、标题是什么、剪贴板怎么交互,它们一起构成了“终端这块黑魔法”的底层语言。
从遇到“cat 一下终端就变色”的困惑,到第一次读懂 \x1b]4;0;#1A1A25\x1b\,中间其实只隔着一层不算太厚的协议说明。一旦这层窗户纸捅破,你就会发现:那些看似莫名其妙的“乱码”,其实都是终端世界里非常讲理的语法而已。