平时使用第三方npm库开发时经常看到终端打印不同颜色的提示信息,觉得美观的同时,你有想过其中的原理吗?
体验
首先,你可以尝试在bash shell中执行以下命令
1
echo -e "\033[31m前端斟茶兵\033[0m"
或者用node执行js文件
1
2
// test.js
console.log('\033[31m前端斟茶兵\033[0m')
两个命令的结果是一样的,都在终端输出了红色的字体,虽然有点粉😂 为什么用\033[31m
这种奇怪的字符包裹文本就能显示特殊颜色呢?这时就需要ANSI转义序列登场了。
ANSI转义序列
这是美国国家标准学会在上世纪70年代制定的标准,并在80年代以来的计算机设备上广泛使用,目前DOS和Unix类的终端模拟器基本都支持ASNI转义序列。
根据维基百科的介绍,这是一种带内信号的转义序列标准,用于控制视频文本终端上的光标位置、颜色和其他选项。在文本中嵌入确定的字节序列, 大部分以ESC转义字符和”[“字符开始,终端会把这些字节序列解释为相应的指令,而不是普通的字符编码。
常用的控制类型ASCII码
以下表格列出0-31位用于控制的不可打印ASCII码
名称 | ASCII码 | 八进制 | 十六进制 | C类型转义字符 | Ctrl键 | 描述 |
---|---|---|---|---|---|---|
BEL | 7 | 007 | 0x07 | \a | ^G | 终端铃声 |
BS | 8 | 010 | 0x08 | \b | ^H | 回退 |
HT | 9 | 011 | 0x09 | \t | ^I | 水平Tab |
LF | 10 | 012 | 0x0A | \n | ^J | 换行 |
VT | 11 | 013 | 0x0B | \v | ^K | 垂直Tab |
FF | 12 | 014 | 0x0C | \f | ^L | 换页 |
CR | 13 | 015 | 0x0D | \r | ^M | 回车 |
ESC | 27 | 033 | 0x1B | \e | ^[ | 转义字符 |
文章开头给出的例子中033
其实是转义字符ESC
的八进制表示,对应的十六进制表示为0x1b
或0x1B
,C语言的转义字符为\e
,ASCII码为27
。
前面的例子改成十六进制或者\e
的写法也是可以的,以下3个命令效果一样。
1
2
3
echo -e "\x1b[31m前端斟茶兵\x1b[0m"
echo -e "\x1B[31m前端斟茶兵\x1B[0m"
echo -e "\e[31m前端斟茶兵\e[0m"
注意:最好不要用
\e
作为转义字符使用,因为不是所有语言和编译器都支持它,比如Python。十六进制表示有时也会有问题,比如下文说到的重置状态。重置状态的转义序列的十六进制表示为\x1bc
,它是一个合法的十六进制数,所以终端会把它作为一个整体解析,没有起到\033c
的重置作用。
后文会用
ESC
表示\033
或者\x1b
。
ESC
与@A-Z[\]^_
范围的字符(C1控制字符5)组合,我们称之为转义序列。
部分ANSI转义序列
序列 | C1控制字符 | 名称 | 描述 |
---|---|---|---|
ESC N | 0x8e | SS2 - Single Shift Two | 下一个字符从G2可打印字符集选择一个字符 |
ESC O | 0x8f | SS3 - Single Shift Three | 下一个字符从G3可打印字符集选择一个字符 |
ESC P | 0x90 | DCS - 设备控制字符串(Device Control String) | 控制设备 |
ESC [ | 0x9b | CSI - 控制序列导入器(Control Sequence Introducer) | 大部分有用的序列 |
ESC \ | 0x9c | ST - 字符串终止(String Terminator) | 终止其他控件(APC、DCS、OSC、PM和SOS)中的字符串 |
ESC ] | 0x9d | OSC - 操作系统命令(Operating System Command) | 启动操作系统使用的控制字符串 |
ESC X | 0x98 | SOS - 字符串开始(Start of String) | 引用由ST终止的一串文本的参数 |
ESC ^ | 0x9e | PM - 私有消息(Privacy Message) | |
ESC _ | 0x9f | APC - 应用程序命令(Application Program Command) | |
ESC c | RIS - 重置为初始状态(Reset to Initial State) | 重置图形格式,清除制表符,重置为默认字体等 |
重置状态的转义序列八进制表示为
\033c
,十六进制表示为\x1bc
,但是\x1bc
是个合法的十六进制数,所以它不会像
CSI序列
ESC
与[
的组合我们称为CSI,以CSI为开头的序列则称为CSI序列,又叫ANSI控制序列。
后文会用
CSI
表示ESC[
。
CSI + 0或n个参数字节 + 0或n个中间字节 + 1个最终字节组成了CSI序列。
CSI序列后半部分的字符范围
组成部分 | 字符范围 | ASCII | |
---|---|---|---|
参数字节 | 0x30-0x3F | 0-9:;<=>? | |
中间字节 | 0x20-0x2F | 空格、!"#$%&'()*+,-./ | |
最终字节 | 0x40-0x7E | @A-Z[\\]^_ a-z{ | }~` |
CSI序列会忽略超出0x20–0x7E范围的字符。
CSI序列后半部分不同的组合代表着不同的功能,这里列举一些例子。
光标控制
CSI序列 | 描述 |
---|---|
CSI H | 光标移至(0, 0)位置 |
CSI nA | 光标上移n行 |
CSI nB | 光标下移n行 |
CSI nC | 光标右移n列 |
CSI nD | 光标左移n列 |
CSI nE | 光标移至下一行行首,下移n行 |
CSI nF | 光标移至上一行行首,上移n行 |
CSI nG | 光标移至第n列 |
CSI n;mH | |
CSI n;mf | 光标移至n行m列 |
CSI s | 保存光标位置,非标准,建议用ESC7 |
CSI u | 恢复上次保存的光标位置,非标准,建议用ESC8 |
擦除功能
CSI序列 | 描述 |
---|---|
CSI J | 清除屏幕 |
CSI 0J | 清除光标到屏幕末尾的内容 |
CSI 1J | 清除光标到屏幕初始的内容 |
CSI 2J | 清除整个屏幕 |
CSI K | 清除当前行 |
CSI 0K | 清除光标到行末的内容 |
CSI 1K | 清除光标到行首的内容 |
CSI 2K | 清除整行 |
屏幕模式
CSI序列 | 描述 |
---|---|
CSI =0h | 40 x 25 黑白(文本) |
CSI =1h | 40 x 25 彩色(文本) |
CSI =2h | 80 x 25 黑白(文本) |
CSI =3h | 80 x 25 彩色(文本) |
CSI =4h | 320 x 200 4色(图像) |
CSI =5h | 320 x 200 黑白(图像) |
CSI =6h | 640 x 200 黑白(图像) |
CSI =7h | 允许换行 |
CSI =13h | 320 x 200 彩色(图像) |
CSI =14h | 640 x 200 彩色(16色图像) |
CSI =15h | 640 x 350 黑白(2色图像) |
CSI =16h | 640 x 350 彩色(16色图像) |
CSI =17h | 640 x 480 黑白(2色图像) |
CSI =18h | 640 x 480 彩色(16色图像) |
CSI =19h | 320 x 200 彩色(256色图像) |
CSI ={value}h | 将屏幕宽高或类型改成value对应的模式 |
CSI ={value}l | 最终字符是小写L,重置value对应的模式,如果value为7,则禁止换行 |
键盘字符串
ESC[{code};{string};{...}p
的模式可以将某个键盘键位重定义成指定的字符串。具体可以参考资料2。
从上面可以看出,当最终字符为A-H
时,表示光标移动。当最终字符为J
或K
时,表示擦除。当最终字符为h
,参数字节为=
时,表示屏幕模式。
而当最终字符为m
,中间没有参数或者参数以;
分隔的CSI序列,就是我们要重点讲的SGR
。
SGR
Select Graphic Rendition,选择图形再现,可以设置终端文本样式,包括颜色、粗细、斜体、下划线等。
常用样式设置
下表列举了除颜色外的常用样式的CSI序列
CSI序列 | 重置序列 | 描述 |
---|---|---|
CSI 0m | 重置所有样式模式 | |
CSI 1m | CSI 21m | 粗体 |
CSI 2m | CSI 22m | 弱化 |
CSI 3m | CSI 23m | 斜体 |
CSI 4m | CSI 24m | 下划线 |
CSI 5m | CSI 25m | 缓慢闪烁 |
CSI 6m | CSI 26m | 快速闪烁 |
CSI 7m | CSI 27m | 反显,前景色背景色交换 |
CSI 8m | CSI 28m | 隐藏 |
CSI 9m | CSI 29m | 划除 |
8/16色
颜色 | 前景色代码 | 背景色代码 |
---|---|---|
黑 | 30 | 40 |
红 | 31 | 41 |
绿 | 32 | 42 |
黄 | 33 | 43 |
蓝 | 34 | 44 |
品红 | 35 | 45 |
青 | 36 | 46 |
白 | 37 | 47 |
默认 | 39 | 49 |
重置 | 0 | 0 |
30-37是标准的8种标准前景色,40-47是对应的背景色,39或49可以重置颜色。没有提及的38和48是用来设置256色和RGB颜色的,后文会说。
对于支持aixterm规范3的终端,还有另外的8种高强度色。
颜色 | 前景色代码 | 背景色代码 |
---|---|---|
亮黑 | 90 | 100 |
亮红 | 91 | 101 |
亮绿 | 92 | 102 |
亮黄 | 93 | 103 |
亮蓝 | 94 | 104 |
亮品红 | 95 | 105 |
亮青 | 96 | 106 |
亮白 | 97 | 107 |
如果要设置前景是红色,背景是白色的粗体文本,可以这么写
1
\033[1;31;47m
对不同终端来说,同一个颜色代码显示出来的颜色可能会有差异。
256色
CSI序列 | 描述 |
---|---|
CSI38;5{ID}m | 设置前景色 |
CSI48;5{ID}m | 设置背景色 |
5表示使用256色,ID是0-255中的一个值,可以理解为颜色的序号。
256色的计算方式
ID | 对应关系 |
---|---|
0-7 | 对应8/16色中的标准色30-37 |
8-15 | 对应8/16色中的高强度色90-97 |
16-231 | 216种,计算公式:16 + 36 × R + 6 × G + B (0 ≤ R, G, B ≤ 5) |
232-255 | 24阶灰度色 |
RGB色
CSI序列 | 描述 |
---|---|
CSI38;2;{R};{G};{B}m | 设置前景色 |
CSI48;2;{R};{G};{B}m | 设置背景色 |
2表示使用RGB色,支持真彩色(24位RGB)的终端才能使用。R、G、B取值在0-255区间,做前端的同学应该很熟悉了。
\033[0m
回想文章开头的例子,我们用到了\033[0m
,就是为了重置所有样式。更简洁的写法是\033[m
,因为没有参数的话会默认参数为0
。
当然,如果只设置了颜色或者只想重置颜色,可以用\033[39m
或者\033[49m
来重置。
不重置样式可以吗?当然可以。但是如果不重置样式,又没有设置新样式的话,后面的输出都会继承前面的样式。比如node运行下面的代码
1
2
3
// test.js
console.log('\033[31m前端斟茶兵');
console.log('前端斟茶兵');
第二句打印也会有颜色,这可能不是我们想要的。
chalk
chalk是非常有名的在终端显示颜色的nodejs库,它是怎么做的呢?
调试发现,它封装了一个叫ansi-styles6的库,这个库同样遵循转义序列的规范,具体可以看代码。不过,它使用unicode \u001B
来表示ESC
。
总结
- 转义字符
ESC
有多种表示方式:\033
,\x1b
,\x1B
,\e
和\u001B
; ESC
和一些特定字符组合成为转义序列(Escape Sequence),ESC
和[
组成CSI
(控制序列导入器);- CSI序列可以控制终端行为,当最终字符为
m
,中间参数用;
分隔,则是SGR模式,可以设置终端文本的颜色、粗细等样式,一般写成\033[{参数1};{参数2};{...参数n}m
这种格式; - 不同的终端对CSI序列的支持程度不一样,上面讲到的样式可能看不到效果,颜色显示可能也有差异;
最后,上一个效果图
是不是很酷啊!
参考资料
- What does printf(“\033c”) mean: https://stackoverflow.com/questions/47503734/what-does-printf-033c-mean/47503782
- ANSI Escape Sequences: https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797
- ANSI转义序列:https://zh.wikipedia.org/wiki/ANSI%E8%BD%AC%E4%B9%89%E5%BA%8F%E5%88%97
- aixterm规范:https://sites.ualberta.ca/dept/chemeng/AIX-43/share/man/info/C/a_doc_lib/cmds/aixcmds1/aixterm.htm
- C1控制字符:https://zh.wikipedia.org/wiki/C0%E4%B8%8EC1%E6%8E%A7%E5%88%B6%E5%AD%97%E7%AC%A6#C1%E6%8E%A7%E5%88%B6%E5%AD%97%E7%AC%A6%E9%9B%86
- asni-styles: https://github.com/chalk/ansi-styles