使用 ANSI 转义码随心所欲地操纵你的终端(改变输出文字颜色、彩虹渐变色、高亮、加粗、移动光标、隐藏光标等)

不会有人还不知道终端是什么吧?

简而言之,就是类似以下这样的黑框:

关于 ANSI 转义码

什么是 ANSI 转义码?

ANSI 转义码是一种用于格式化文本输出到终端(或控制台)的控制序列。这些序列以特定的字符开始,通常是 ESC(ASCII 码 27),后面跟随左方括号 [ 和一系列参数,最后以字母 m、H 等结束。它们可以用于设置文本的颜色、样式、背景以及控制光标的位置等。

这些转义码的标准通常可以追溯到 ANSI X3.64 标准,后来被一些其他标准(如 ISO 6429)扩展。ANSI 转义码被广泛应用于许多现代终端和仿真环境中。

以上这些是较严谨的解释,要不累子来解释的话,就是类似 \n \t 之类的转义字符而已。

常见的 ANSI 转义码功能

  • 文本样式:如加粗、下划线、闪烁等。
  • 颜色设置:包括前景色和背景色。
  • 光标控制:移动光标、保存和恢复光标位置等。
  • 屏幕清除:清除整个屏幕或清除光标所在行。

支持 ANSI 转义码的终端

许多现代终端和仿真环境都支持 ANSI 转义码,包括但不限于:

  1. Linux 终端
  2. 大多数 Linux 发行版的默认终端(如 GNOME Terminal、Konsole、Xterm 等)都支持 ANSI 转义码。
  3. macOS 终端

macOS 的 Terminal 和 iTerm2 等终端都支持这些代码。

  1. Windows 终端
  2. 跨平台的终端仿真器

总而言之就是:大多数终端都支持

但是不同终端对ANSI的支持率不一样,大多数终端虽然支持ANSI,但也只是支持部分,本文仅详细介绍一些广泛支持的转义符,其余的不累子将举出作用,有兴趣的朋友们可以自己在不同环境下尝试

并且由于不累子测试时仅使用 VS2022 的终端(下文所述效果均为VS2022中的测试效果),且使用 C 语言编写测试代码,所得结果有些出入也很正常。

注意事项

虽然许多现代终端支持 ANSI 转义码,但某些老式终端(如早期版本的 Windows CMD)可能并不支持。如果你的程序需要广泛的兼容性,最好在代码中添加一些检测机制,确保在不支持 ANSI 的环境中不使用这些转义码。

VS2022终端中样式、颜色效果一览

基本格式\033[#m

让我们先看看这些转义码的效果,再来详细介绍

在VS2022中我使用以下代码打印出这些效果(这里看不懂这些代码也没关系,看完了下面的再回来看就明白了):

#include<stdio.h>
int main()
{
	int i = -14;
	for (i = -14; i <= 150; i++)
	{
		printf("\033[%dmthe quick brown fox jumps over a lazy dog\033[0m\t%d\n", i, i);
	}//这里看不懂这些代码也没关系,看完了下面的再回来看就明白了

	return 0;
}

注:左边为文本效果,右边数字为 \033[#m 中的 #

如:第一行就是 \033[-14m 的效果

许多特定的值不会有特殊效果,如-14 ~ 0,10 ~ 20,22 ~ 29,38 ~ 39,48 ~ 51等

这里我将这些值都列出来是为了让大家更连续地看不同值的效果

一些容易引起误解的转义码的功能:2 为暗淡,5、6 为闪烁,8 为隐形文字,30 为黑色文字,37 为白色文字,40 为黑色背景,53为上划线

再次强调:许多特定的值不会有特殊效果,如-14 ~ 0,10 ~ 20,22 ~ 29,38 ~ 39,48 ~ 51等

这里我将这些值都列出来是为了让大家更连续地看不同值的效果

VS2022中,\033[5m和\033[6m效果相同,都为闪烁

256色模式的256种颜色展示

运行代码:

#include<stdio.h>
int main()
{
	int i = -10;
	for (i = -10; i < 260; i++)
	{
		printf("\033[38;5;%dmBLZ\033[0m\t", i);
		printf("\033[48;5;%dm   \033[0m\t", i);
		printf("%d\n", i);
	}
	return 0;
}

这里我同样稍微将打印的范围拓展了一下,可知当 # <= -1 或 # >= 256 时,\033[38;5;#m 不具有特殊效果

注:0、16、232的效果为纯黑,所以在黑色背景下看不到他们的前景色和背景色

从左到右三列依次为:

\033[38;5;#m        \033[48;5;#m        #

真彩色的渐变

这里的演示代码在下方“真彩色(24-bit)”一节会介绍

文本-样式

常规

\033[0m  重置所有属性

重置所有格式设定,包括颜色、加粗、下划线等

最上面我写的那个指令,printf 末尾就用了这个来重置格式,防止不同格式间相互干扰。

如果你看到这还是没法理解,那就继续往下看

样式

\033[1m  加粗文字

在 VS2022 中看起来就像高亮,并没有看出加粗的效果

\033[2m  暗淡文字(在某些终端中可能不明显)

暗淡文字转义符使得使得文字变成灰色,这和后面的改变文字颜色类似

\033[3m  斜体文字(部分终端支持)

在这个格式下,a 等字母是以手写体打印的,而不是印刷体

\033[4m  单下划线

\033[5m  \033[6m  闪烁效果(部分终端支持)

在 VS2022 中约 1s 闪烁一次,颜色变化为 白->灰->白

\033[7m  反显(背景色和前景色互换)

\033[8m  隐形文字

文字看起来就像真的消失了一样,但当你 ctrl + c 复制后再 ctrl + v 粘贴到别处就能看到文字

\033[9m  删除线

补充:

\033[21m  双下划线

\033[53m  单上划线

文本-颜色

基本颜色(前景色和背景色)

注:前景色就是文字本身的色彩,背景色就是文字背景的色彩

前景色

\033[30m  黑色        BLZ

\033[31m  红色        BLZ

\033[32m  绿色        BLZ

\033[33m  黄色        BLZ

\033[34m  蓝色        BLZ

\033[35m  紫色        BLZ

\033[36m  青色        BLZ

\033[37m  白色        BLZ

背景色

\033[40m  黑色             

\033[41m  红色             

\033[42m  绿色             

\033[43m  黄色             

\033[44m  蓝色             

\033[45m  紫色             

\033[46m  青色             

\033[47m  白色             

256色模式

前景色

\033[38;5;#m        使用 256 色中第 # 种颜色作为前景色

背景色

\033[48;5;#m        使用 256 色中第 # 种颜色作为背景色

总结

由上面的展示可知,256色中的256种颜色并不是按彩虹渐变的顺序排列的,这时你也许会烦恼:难道就没有什么办法实现彩虹渐变色吗?

你或许看到了上面的真彩色渐变展示,没错,使用真彩色可以实现彩虹渐变色

真彩色(24-bit)

什么是真彩色/RGB?

介绍详见:真彩色/RGB是什么?-CSDN博客

前景色

\033[38;2;R;G;Bm  使用 RGB 值设置前景色

背景色

\033[48;2;R;G;Bm  使用 RGB 值设置背景色

光标控制

体现:在使用本节的相关转义码时,请注意其中英文字母的大小写

重要:光标的行为

在讲光标控制转义符时,我们先讲一讲终端中光标的行为

终端中的光标,看起来就像是一个在两个字符中间闪烁的 | :

但它的实际行为真的如此吗?

其实,终端中光标的实际行为并不像是这样,其实它实际应该是这样:

怎么理解这二者的区别呢?很简单,终端中的光标其实并不像看起来那样位于两个字符中间而没有选择目标,实际上光标选择了其后的第一个字符

即:终端里的光标是选择了一个字符的

也许你还是不理解,那么我接下来就上一个实例:

#include<stdio.h>
int main()
{
	printf("*****\n");
	printf("*****\n");
	printf("*****\n");
	printf("*****\n");

	printf("\033[2;2H");//将光标移动到 2 行 2 列

	printf("B");
	printf("L");
	printf("Z");

	return 0;
}

终端里该代码的运行过程拆解为:

但如果光标的行为真的和显示的一样,结果就会是这样:

而如果光标的实际行为不像显示的那样,而是选择了某个字符,结果就符合实际:

所以,我们不难得知,终端里的光标,它的实际行为是针对某个字符操作的

如果你还不明白这个结论,可以往下翻到 “ 光标控制 / 清屏和清行 / 清除行 ” 

如果你理解了,开始疑惑:为什么不累子要先阐明这一点呢?

那......那你也得往下看到 “ 光标控制 / 清屏和清行 / 清除行 ” 才会明白

(请原谅不累子的顽皮qwq)

光标移动

(功能类似键盘上的方向键,但仍略有差别,详见后文)

\033[#A  向上移动 #

\033[#B  向下移动 #

\033[#C  向右移动 #

\033[#D  向左移动 #

注意

1

向上/向下移动为垂直移动,不改变光标的水平位置,怎么理解呢?看实例:

#include<stdio.h>
int main()
{
	printf("BLZ");
	printf("\033[3B");//光标向下移动 3 行
	printf("BLZ");
	return 0;
}

终端输出结果:

BLZ


   BLZ
D:\Project\2024.10.10\x64\Debug\2024.10.10.exe (进程 16292)已退出,代码为 0 (0x0)。
按任意键关闭此窗口. . .

可以看到最下面输出的 BLZ 并不是从行首开始的,而是与最上面输出的 BLZ 的末尾对齐

可见向下移动光标是不会改变光标的水平位置

向上也是同理,这里不累子就不做过多演示了

这里再插入一段有意思的代码:

#include<stdio.h>
int main()
{
	printf("\033[5B");//向下 5 行

	printf("BLZ");//打印 BLZ

	printf("\033[5A");//向上 5 行
	return 0;
}

运行结果:


D:\Project\2024.10.10\x64\Debug\2024.10.10.exe (进程 5416)已退出,代码为 0 (0x0)。
按任意键关闭此窗口. . .


BLZ

可以看见打印出的 BLZ 在结束信息之下,是不是很神奇?

这是因为

对于 VS2022 自带的终端,它的结束信息是根据程序结束时光标所在的位置进行输出的(一般是在结束时光标所在行的下一行),上面的代码先将光标向下移动 5 行打印 BLZ 再把光标向上移动,然后程序结束

于是我们不难得知,由于在程序结束时光标在第一行,所以结束信息自然从下一行开始显示,显示完正好在打印出的 BLZ 上方

 2

这些光标移动转义符和键盘上按下方向键的效果仍然有细微差别,具体表现为在以下这段文本中:

BLZ|BLZBLZ

//注:| 表示当前光标所在位置

可以看到上面的文本只有一行

当我们一直按下方向右键时:最终光标会停在文本末尾而不会继续往右

但使用转义码右移光标  时:即使光标到达了文本末尾,其右边没有任何字符,光标仍能继续往右移动

对于上下移动光标的转义码也是同理:

当使用向上或向下的方向键时,光标并不会换行

但使用转义码时:即使光标所在上下行并没有任何字符,其仍能上下移动换行

哦,当光标已经位于第一行时,向上移动光标的转义码并不会产生实际效果

光标位置

\033[6n  请求当前光标位置      这个指令似乎在 Windows 平台下没有实际作用!!!

\033[;H  移动光标选择到某的字符

\033[H  移动光标到终端最左上角

示例:

#include<stdio.h>
int main()
{
	printf("\033[2;17H");//将光标移动到 2 行 17 列
    printf("BLZ");//打印 BLZ

	return 0;
}

当然,如果你不嫌代码混杂,也可以将它们放到同一个 printf 函数中

#include<stdio.h>
int main()
{
	printf("\033[2;17HBLZ");

	return 0;
}

输出结果:


                BLZ
D:\Project\2024.10.10\x64\Debug\2024.10.10.exe (进程 4292)已退出,代码为 0 (0x0)。
按任意键关闭此窗口. . .

光标显隐

\033[?25l  隐藏光标

\033[?25h  显示光标

当光标未隐藏时:

当代码中加入以下语句后:

#include<stdio.h>
int main()
{
	printf("\033[?25l");//在程序一开头就隐藏光标
    //...其他代码

	return 0;
}

这样你在运行时就不会看到令人讨厌的光标了:

光标位置保存

\033[s  保存当前光标位置

\033[u  恢复光标位置

这两个指令类似计算中的记忆功能

只能记忆最后一次保存光标时光标的位置信息

即:后一次保存的信息将覆盖前一次保存的信息

实例代码:

#include<stdio.h>
int main()
{
	printf("这是第一次保存的位置->\033[s");//第一次保存
	printf("\n\n\n");
	printf("这是第二次保存的位置->\033[s");//第二次保存
	printf("\n\n\n");
	printf("\033[u");//返回上一次保存的位置
	printf("这是打印的信息");
	printf("\n\n\n");//让烦人的结束信息离远点

	return 0;
}

运行结果:

清屏和清行

清屏

\033[2J  清除整个屏幕

注:不会改变光标位置:

#include<stdio.h>
int main()
{
	printf("BLZ\n");
	printf("BLZ\n");
	printf("BLZ\n");
	printf("BLZ\n");
	printf("BLZ\n");
	printf("\033[2J");
	printf("123");

	return 0;
}

运行步骤拆分:

 可见清屏指令是不会改变光标位置的

清除行

\033[0K    \033[K  清除光标之后的内容

\033[1K  清除光标之前的内容

\033[2K  清除光标位置所在的行的内容

注意点

以下代码:

#include<stdio.h>
int main()
{
	printf("*****\n");
	printf("*****\n");
	printf("*****\n");
	printf("*****\n");
	printf("\033[2;2H");
	printf("\033[1K");
	printf("\n\n\n");//防止结束信息覆盖打印的内容

	return 0;
}

一步步拆分运行步骤后 (注意看清除的内容) :

那么问题来了:\033[1K  难道不是清除光标之前的内容吗?怎么光标后面的一个字符也被清除了?

这里就要结合我们上文的 “ 光标控制 / 重要:光标的行为 ” 一节中的结论来看了

当运行完 \033[2;2H 后

光标实际上选择了第 2 行第 2 列的字符

而不是像看起来那样被插入到第 2 行的第 1、2 个字符中间

于是当我们使用 \033[1K 清除光标前的字符时,第 2 行第 2 个字符也被清除了

这里不累子给出建议:当你需要使用 \033[1K 时,请注意考虑终端里光标的实际行为,以免不小心清除了你想保留的内容

打印彩虹渐变色

由于24位真彩色所能表现的颜色种类非常多(共 256 * 256 * 256 == 16,777,216 种)

并且由于R、G、B值的连续变化可以使颜色变幻更加平滑

我们可以使用真彩色的 ANSI 转义码打印出彩虹渐变色的效果( -> -> -> 绿 -> -> -> -> )

具体代码如下:

#include<stdio.h>
#include<Windows.h>//暂停函数 Sleep() 的头文件
int main()
{
	int r = 255;
	int g = 0;
	int b = 0;
	printf("\n\n\n\n");//美化显示格式
	printf("\t\t r\t g\t b\n");//美化显示格式
	
    for (;;)//关键循环
	{
		if (255 == r && g < 255 && 0 == b)//多个分支实现渐变色的循环
		{
			g++;
		}
		else if (255 == g && r > 0 && 0 == b)
		{
			r--;
		}
		else if (0 == r && b < 255 && 255 == g)
		{
			b++;
		}
		else if (0 == r && g > 0 && 255 == b)
		{
			g--;
		}
		else if (0 == g && r < 255 && 255 == b)
		{
			r++;
		}
		else if (255 == r && 0 == g && b > 0)
		{
			b--;
		}

		printf("\033[?25l\033[38;2;%d;%d;%dmBLZ\033[0m\033[?25l\t\033[48;2;%d;%d;%dm    \033[0m\033[?25l\t%3d\t%3d\t%3d\n\033[1A", r, g, b, r, g, b, r, g, b);//分析详见下文
		Sleep(1);//暂停1ms,详见下文
	}



	return 0;
}

1.为什么每种颜色要暂停显示 1ms ?

终端运行该程序的速度很快,如果不暂停,将会出现以下情况:

2.printf 里面的一大串是什么东西

我把这串代码拆成了不同组成单元,并且不同组成单元之间我加上了 ||| 符号,现在你可以听不累子给你一一解读了:

printf("\033[?25l|||\033[38;2;%d;%d;%dmBLZ\033[0m|||\033[?25l|||\t|||\033[48;2;%d;%d;%dm|||    \033[0m|||\033[?25l|||\t|||%3d|||\t|||%3d|||\t|||%3d|||\n|||\033[1A", r, g, b, r, g, b, r, g, b);

背景色为黄色的组成部分是 ANSI 转义码中的光标操纵转义码

\033[?25l        作用为隐藏光标

\033[1A           作用为将光标移至上一行(不改变光标水平位置)

背景色为红色的组成部分是 ANSI 转义码中的格式重置转义码

\033[0m           作用为重置文本格式(包括重置文本颜色和取消加粗、下划线、删除线等)

背景色为蓝色的组成部分是打印出的内容

唯一值得一提的是:%3d 中的数字 3 表明了一个打印出的整型占 3 个字符的宽度

且由于 3 是正数,还表明这是右对齐打印

(如果是%-3d则为对齐打印,一个打印出的整型占 3 个字符的宽度)

背景色为橙色的组成部分是 C 语言中的转义字符

\t        为水平制表符

\n        为换行符

背景色为紫色的组成部分是......这都不懂的话建议重学 C 语言......

一些不受广泛支持的转义码

在这里请原谅 BLZ 无法展示这些转义码的功能,它们在不累子 Win11+VS2022 的环境下并没有实际效果,我建议大家看看就行,毕竟它们不是通用的

\033[?1049h  进入备用屏幕

\033[?1049l  返回主屏幕

\033[?1047h  进入替代屏幕(有些终端支持)

\033[?1047l  退出替代屏幕

\033[10m  暖色背景,效果往往未被广泛实现,某些终端可能不会有明显效果

\033[11m  反向闪烁(Slow Blink),但支撑程度有限,很多现代终端忽略

\033[12m  反向亮显(Rapid Blink),这个通常在大多数终端上不起效

\033[13m  类似于 Bright,效果未被普遍实现

\033[14m  常规显色,通常表现为普通文本显示

\033[15m  尝试更新背景色与正文,但实际效果因终端而异

\033[16m  不累子甚至查不到它的作用!!

\033[17m  反转色彩(Negative),即反转当前的前景和背景颜色。支持情况不一

\033[18m  允许使用不同的显色,现代终端可能未实现此功能

\033[19m  非常规的闪烁,实际效果因终端而异

\033[20m  设置为无闪烁,常用于关闭闪烁效果,但许多终端具有不同的处理方法

\033[22m  关闭加粗,或取消任何突出的效果

\033[23m  关闭下划线效果

\033[24m: 关闭闪烁效果

\033[25m  关闭删除线效果

\033[26m  一般用于控制某种文本样式,支持情况不一

\033[27m  响应式文本效果,实际支持情况较少

\033[28m  掩盖(Reveal),但具体现象与终端设备有关

\033[29m  关闭掩盖效果

总结

ANSI 转义码也许在工作中并不常见,但对于许多 C 语言或者其他上面语言的初学者来说,使用它来美化你的一个小游戏也许是一个很不错的选择,比如不累子的扫雷游戏:

在给它上色之前,我都不知道一个黑框能装下这么美的程序

 哦,这个扫雷游戏可有意思了,我添加了标记功能(插旗子),并且还能像windows自带的扫雷那样点开一格出来一大片,并且,你也看到了,它是彩色的!

如果你也想制作这样的一款扫雷游戏,那么现在就关注不累子吧!在介绍完三子棋后,不累子就会一步步教大家(包教包会!!)制作这样一款扫雷哦~

建议收藏起来慢慢看!!

(然后顺手点赞关注一下不过分吧)

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值