本篇文章介绍如何在代码中使用 ANSI 转义码来设置终端的字符显示颜色、移动光标位置等,并实现一个进度条百分比跳变的效果。
ANSI 转义码
在 Linux 中,可以使用 ANSI 转义码(ANSI escape codes)设置终端的字符显示颜色、移动光标位置、清除字符显示等。
ANSI 转义码是由终端自身支持,独立于编程语言之外,可以在 C 语言、Java、Python、或者 Shell 中使用。
下面以 bash shell 为例来说明如何使用 ANSI 转义码。
ANSI 转义码格式
ANSI 转义码由一串 ASCII 编码的字符串组成,要求以 ASCII 编码的 Escape 字符和 [ 字符开头,后面跟着具体的转义码,指定相应的操作。基本格式如下:
Esc[escape code
Escape 字符也就是 Esc 键对应的字符。
由于按 Esc 键,不会得到一个可显示的字符,需要用具体的编码值来表示这个字符。
在不同编程语言中,表示字符编码值的写法可能不一样。一般常用 e 转义字符来表示 Esc 字符。
使用 echo 命令测试 ANSI 转义码
在 bash shell 中,可以使用 echo 命令的 -e 选项来测试 ANSI 转义码。
查看 man echo 对 -e 选项说明如下:
-e
enable interpretation of backslash escapes.
If -e is in effect, the following sequences are recognized:
e
escape
0NNN
byte with octal value NNN (1 to 3 digits).
即,在 echo 命令中,-e 选项可以指定处理转义字符。
e 转义字符表示 escape 字符。
0NNN 转义字符使用八进制来获取 NNN 编码值对应的字符。
在 ASCII 编码中,Escape 字符对应的八进制值是 033。
则在 echo 命令中,033 表示 escape 字符。
使用 echo 命令测试 ANSI 转义码时,可以写为 echo -e "033[31m"。
这里的 31m 转义码表示要把终端字符的前景色设成红色。
Linux 的 printf 命令也可以输出 ANSI 转义码,而且不需要加 -e 选项,例如写为 `printf "e[31m"。
注意:这里需要用双引号、或者单引号把 033[31m 括起来,避免 bash 自身对 进行转义,会去掉 字符,导致 echo 命令收不到 字符,无法处理转义字符。
也可以写为 echo -e "e[31m",e 也表示 escape 字符。
后面测试的时候,统一使用 e 的形式,少输入一些字符。
具体测试如下:
![71b932f261b1f5fbc1e31a82e51c5133.png](https://i-blog.csdnimg.cn/blog_migrate/5dc549bc6d058659be65c1c100147dc7.jpeg)
使用ANSI转义码设置终端的字符显示颜色
执行 echo -e "e[31m" 命令后,终端的提示字符会变成红色,之后输入的字符也都会变成红色。
即,终端的默认字符颜色变成了红色。
执行 echo -e "e[0m" 命令重置终端属性,让终端的字符颜色变成原来的默认颜色。
这里的 0m 转义码表示重置字符显示属性。
一般来说,为了不影响终端自身的显示,使用 ANSI 转义码设置某个字符串的显示颜色后,建议随后使用 0m 转义码来重置为原来的颜色。
举例说明如下:
![62cda5d59acd4194e8b54eb1b4b1bee0.png](https://i-blog.csdnimg.cn/blog_migrate/600722a04963efd1a51a0a6eac17369d.jpeg)
只修改指定字符串的显示颜色
在上面命令中,e[31m 是一个 ANSI 转义码,表示设置终端字符颜色为红色。
e[0m 也是一个 ANSI 转义码,表示重置终端的颜色属性,会恢复成原来的颜色。
在这两个转义码中间的字符串会显示在终端上。
执行该命令后,终端的提示符会显示为原来的颜色。
设置终端字符颜色的 ANSI 转义码
下面详细说明设置终端字符颜色的 ANSI 转义码,其基本格式如下:
Esc[Value;...;Valuem
这里的 Value 可以提供多个值,不同值之间用分号 ‘;’ 隔开。
这些值可以分别指定字符的前景色、背景色、字符属性(粗体、下划线、反转)。它们之间的顺序不限。
转义码最后以 m 字符结尾。
设置字符前景色的值如下:
![3c474994b67eedc99b9568e269de310c.png](https://i-blog.csdnimg.cn/blog_migrate/6c6696eaf59e3bbba125c9a238dd0cb8.jpeg)
ANSI转义码设置字符前景色
设置字符背景色的值如下:
![74d422f3d8645b223b85a304cdfb0c75.png](https://i-blog.csdnimg.cn/blog_migrate/0aed104dc88d63ef2474d239a4095890.jpeg)
ANSI转义码设置字符背景色
设置字符属性的值如下:
![58b619657432cb85a84d47b6c6230ae1.png](https://i-blog.csdnimg.cn/blog_migrate/2e062e7a19ab7192a71b7bd29d1c2bbf.jpeg)
ANSI转义码设置字符显示属性
具体举例如下:
![3797ee2446a2f61f876069376062a918.png](https://i-blog.csdnimg.cn/blog_migrate/2c55b9acdb54fe275ecf276064e7a61e.jpeg)
设置字符串显示的前景色和背景色
可以看到,e[31;44m、e[44;31m 这两个转义码设置的字符颜色效果是一样的。所给的前景色、背景色没有要求先后顺序。
目前的大部分终端都支持 256 色,可以使用 Esc[38;5;Valuem 来设置终端字符为 256 色。这里的 Value 取值是 0-255。
例如,echo -e "e[38;5;111mAAAAAAe[0m" 命令设置为 111 对应的颜色。
具体的颜色取值可以查看 256 色的颜色表。网上的很多文章都有说明。这里不再列举。
使用 ANSI 转义码移动终端光标
ANSI 转义码可以用来移动终端的光标位置,从而改变字符的输出位置。
具体举例如下:
$ echo -e "123456789e[4Dabc"12345abc9
在这个命令中,e[4D 转义码表示把光标往左移动 4 列。
可以看到,光标移动 4 列后,位于字符 6 所在的位置,重新输出 abc,覆盖了原来的 678 三个字符。
移动光标的具体转义码说明如下:
![22f53ca10ed12ed9c74b474dd01dc9c7.png](https://i-blog.csdnimg.cn/blog_migrate/d8e192131e13c2fb074c8e3f6e9926d4.jpeg)
移动终端光标位置的ANSI转义码
上面所说的终端位置指的是终端可见的窗口位置,不包括缓冲区位置。
即,窗口显示不会发生滚动,只在当前可见的窗口区域跳转光标。
注意:由于 echo 命令默认会输出换行符,导致移动光标后再次换行,会对光标移动效果造成干扰。
在测试移动光标的转义码时,建议用 printf 命令测试。该命令默认不会输出换行符。
由于 bash 里面需要按下回车才执行命令,会影响光标的左右移动效果,建议在 printf 自身输出的内容中左右移动光标。
实际测试发现,光标右移 n 列,光标会位于第 n 列的后面,之后输出的字符串会从 n+1 列开始。
Esc[C、Esc[0C、和 Esc[1C 的效果相同,都是光标右移 1 列。
类似的,Esc[D、Esc[0D、和 Esc[1D 的效果相同,都是光标左移 1 列。
使用 printf 命令测试如下:
$ printf "123456789e[1Da"12345678a$ printf "123456789e[0Da"12345678a$ printf "123456789e[Da"12345678a
可以看到,使用 e[D、e[0D、e[1D 往左移动光标,然后输出字符 a,都是覆盖同一个字符 9。
这三个转义码的光标移动效果相同。
$ printf "123456789e[4Da"12345a789$ printf "123456789e[4De[Ca"123456a89$ printf "123456789e[4De[0Ca"123456a89$ printf "123456789e[4De[1Ca"123456a89
e[4D 把光标左移 4 列,移动到字符 6 的位置。
e[C、e[0C、e[1C 都是往右移动光标到下一列,到字符 7 的位置,输出字符 a,覆盖了字符 7。
通过移动光标实现进度百分比的效果
我们可以通过移动光标实现进度百分比的效果。假设有一个 progress.sh 脚本,内容如下:
#!/bin/bashfor ((i = 0; i <= 100; ++i)); do printf "e[5D%3d%%" $i sleep 0.1sdoneecho
这里使用 printf 命令进行输出,以便格式化字符串。printf 命令也是使用 e 来表示 escape 字符。
e[5D 转义码表示把光标左移 5 列。
由于所输出的字符不超过 5 个字符,每次光标左移 5 列,都会移动到最左边,从第一列开始输出。
那么后面输出的内容会覆盖前面输出的内容,达到在同一行重复输出的效果。
sleep 0.1s 命令表示暂停 0.1 秒。添加这个语句,以便清楚地看到进度百分比跳变。否则执行过快,百分比很快就跳到 100%。
执行 progress.sh 脚本的结果如下:
$ ./progress.sh100%
这里不是动图,看不到进度百分比跳变。实际执行就能看到。
从结果来看,在 for 循环中多次打印信息,这些信息都打印在同一行,并覆盖前面的输出。而不是换行打印。
通过移动光标实现进度条的效果
下面通过移动光标实现进度条的效果。假设有一个 progressbar.sh 脚本,内容如下:
#!/bin/bashfunction print_chars(){ # 传入的第一个参数指定要打印的字符串 local char="$1" # 传入的第二个参数指定要打印多少次指定的字符串 local number="$2" local c for ((c = 0; c < number; ++c)); do printf "$char" done}declare -i end=50for ((i = 1; i <= end; ++i)); do printf "e[80D[" print_chars "#" $i print_chars " " $((end - i)) printf "] %3d%%" $((i * 2)) sleep 0.1sdoneecho
这个脚本定义了一个 print_chars 函数,可以多次打印同一个字符。
printf "e[80D[" 语句把光标左移 80 列。由于这个进度条的字符总长度小于 80,会移动最左边,总是从第一列开始输出。
在 ‘80D’ 后面的 ‘[’ 字符是进度条的开头第一个字符。
print_chars "#" $i 语句递增打印多个 # 字符,形成进度条往前移动的效果。
print_chars " " $((end - i)) 语句打印多个空格,填充到指定的最后一列,让进度条的结束字符总是打印在同一列。
printf "] %3d%%" $((i * 2)) 语句打印进度条的结束字符 ]、以及进度条百分比。
sleep 0.1s 语句暂停 0.1 秒,避免执行过快,看不到进度条的移动效果。
执行 progressbar.sh 脚本的结果如下:
$ ./progressbar.sh[##################################################] 100%
这里不是动图,看不到进度百分比跳变。实际执行就能看到。
使用 ANSI 转义码清屏、清除字符
下面的 ANSI 转义码可以用于清屏、清除光标往后的字符。
![79e89a99a9850ea7cc620fafe0cd5abc.png](https://i-blog.csdnimg.cn/blog_migrate/5fd9640a5de911adde62dfc98dba2d6e.jpeg)
ANSI转义码清除终端字符
注意:上面的 J、K 都是大写字母。
具体举例说明如下:
$ printf "123456789e[5De[K"1234$ printf "123456789e[5De[1K" 6789$ printf "123456789e[5De[2K"
printf "123456789e[5De[K" 命令先光标左移 5 列,停在字符 5 的位置,然后用 e[K 转义码从光标位置往后清除所有字符,只保留了前面的 1234 字符串。
printf "123456789e[5De[1K" 命令使用 e[1K 转义码从光标位置往前清除所有字符,只保留了后面的 6789 字符串。
printf "123456789e[5De[2K" 命令使用 e[2K 转义码清除光标所在的整行内容,输出内容为空。
![34c72d7a3c9dd07d389e5ee9dd17c0a3.png](https://i-blog.csdnimg.cn/blog_migrate/5e901f99d229fad75d7c0a8eda5a2c8d.jpeg)