第三章说,终端模拟器收到字节后按「VT100 的规矩」更新屏幕上的字符表格。这一章把这套规矩摊开看——它就是ANSI 转义序列,一串混在普通字符里、本身不显示、却能指挥终端干活的「指令字节」。你见过的所有终端魔法:彩色输出、满屏乱窜的 vim 界面、原地刷新的进度条,全是它。
先看一个可以亲手验证的例子
在终端里敲:
printf '\033[31m 这段是红的 \033[0m 这段恢复正常\n'
你会看到前半句是红色,后半句正常。拆开看:\033 是 ESC 键对应的那个字节(数字 27),它像个「哨兵」,告诉终端「注意,后面不是普通字,是命令」;[31m 的意思是「从这往后的字符用红色画」;\033[0m 是「一切恢复默认」。
所以「红色文字」这种东西在字节流里根本不存在。存在的只是:几个指令字节(换红色墨水)+ 普通字节(要显示的字)+ 几个指令字节(换回默认墨水)。程序输出颜色,就是往字节流里夹带这些暗号。
暗号家族:不止颜色
这套暗号是一整个家族,常用的大致几类:
- 挪光标:
\033[5;10H= 光标挪到第 5 行第 10 列;\033[2A= 上移 2 行。 - 擦除:
\033[2K= 清掉当前行;\033[2J= 清整屏。 - 改样式:颜色、加粗、下划线、反白。
- 存取光标:记住当前位置,一会跳回来。
有了这几样,整个「全屏应用」就齐了。vim 为什么能占满整个终端、把屏幕分成几块、状态栏永远钉在最后一行?因为它进来时先发出「进入全屏模式」的暗号(术语叫备用屏幕,相当于向终端要一块新黑板,退出时还回原来那块),然后疯狂地用「挪光标 + 重写一行」来局部更新画面——你以为 vim 是个图形界面,其实它只是在一秒几十次地重排字符表格。
进度条同理:\r(回车,光标回行首)加反复重写同一行,就是「原地刷新」;再按完成度换换颜色,就是花哨的进度条。命令行世界的一切「动效」,说穿了都是擦了重写。
为什么 cat 错文件会把终端搞花
每个终端用户都经历过:手滑 cat 了一个二进制文件(比如一张图片、一个压缩包),屏幕瞬间爬满乱码,还伴随几声哔——(那是文件里恰好有数字 7 的字节,在终端的规矩里 7 代表「响铃」)。更糟的是,之后你敲什么显示都是乱的。
现在你能解释这一切了:二进制文件里是随机字节,里面恰好有 ESC 开头的序列,终端老老实实当命令执行了——可能切了奇怪字符集、改了颜色、进了什么模式。这不是 bug,是终端太忠诚:它的工作就是执行字节流里的命令,它分不清哪些字节是你想让它执行的。
救场方法上一章提过:盲敲 reset 回车。它发出的序列能把终端状态机拉回出厂设置。想安全地偷看一个文件里有没有暗号,用 cat -v——它会把不可见字节显示成 ^[ 这样的样子,让你看见字节而不执行字节。
程序怎么知道你的终端懂哪些暗号
一个微妙的问题:历史上终端型号五花八门,各自懂的暗号不完全一样,程序怎么知道该发哪套?答案是一个环境变量 TERM 加一张「终端能力数据库」(terminfo)。TERM 告诉你现在连着的是哪种终端(比如 xterm-256color),程序查数据库就知道「清屏该发哪串字节、最多支持几种颜色」。
这就是为什么 ssh 到某些老系统后 vim 提示 "unknown terminal type",或者为什么 TERM 设错了颜色全乱——程序按错型号发了暗号,对面听不懂。第九章讲 tmux 时你还会见到它:tmux 会把 TERM 改成 screen 或 tmux-256color,因为它在里面又包了一层。
一句话总结:终端显示的一切花样,都是字节流里夹带的 ESC 开头的暗号;终端是个绝对忠诚的执行者,看见暗号就执行——这既是 vim 和进度条的根基,也是 cat 错文件会翻车的根源。
到这儿,「程序 → 字节 → 屏幕」这条线完整了。但还有个角色一直藏在幕后:那个给你 $ 提示符、替你记历史、帮你补全文件名的家伙。下一章把它请到台前。