不知道你是不是也遇到过这种情况:写好的批处理脚本跑起来,满屏黑底白字,重要提示和报错全混在一起,盯半天才找到关键那一行。其实cmd的文本颜色输出一直都有,只是大多数人没认真折腾过。把这个玩明白以后,你可以在批处理菜单里做彩色高亮、把日志按级别染成不同颜色、甚至输出256色和RGB真彩色,界面友好度直接拉满,写自动化脚本和服务器维护时也能一眼定位问题。这篇文章就把cmd彩色输出的所有玩法拆开讲清楚,适合刚接触命令行的新手,也适合想让脚本更好用的老手。
1. 先从核心思路上说清楚:cmd上色到底有几种路子
1.1 默认界面为什么显得“土”
cmd全称Command Prompt,是Windows自带的命令行解释器。自打Windows NT时代起,它默认就是黑底白字,除非你手动去“属性”里改字体颜色和背景色,否则所有输出都长一个样。早期这样设计倒也能用,因为那时候终端里跑的都是纯字符程序,也没有花里胡哨的需求。但现在不同了,我们会在cmd里跑Python脚本、调用Git、执行扫描命令、看服务日志,满屏字符没个重点,效率确实提不上去。
颜色输出的核心价值不是好看,而是信息分层。同一段输出里,路径、错误、成功提示、警告信息如果能用不同颜色区分,人眼扫一遍就能定位问题。尤其是你写一个给同事用的批处理工具,菜单选项用颜色区分出来,别人操作起来也不会一脸懵。
1.2 两种主流上色方案:整窗换肤和局部染色
在cmd里做颜色控制,最常见的有两条路。一条是color命令,它把整个窗口的前景色和背景色一起改,属于“全局换肤”。优点是简单,一条命令就搞定;缺点是粗糙,你没办法在输出中间只把某个词染红。
另一条是ANSI转义序列,也叫VT控制码。它通过向终端输出一段特殊的字符序列来临时改变后续文本的样式,属于“局部染色”。你可以把某一行文字变成绿色,下一行又恢复正常,甚至在同一行内部混合颜色。Windows 10以后系统对被 Powershell、Linux 子系统使用的终端组件做了底层改造,cmd也已经原生支持ANSI转义序列,只是默认有些环境还需要手动打开。
两条路不冲突,实际项目里往往一起用:color设置窗口整体风格,ANSI序列用来做内容级的色彩标记。下面我把这两条路分别拆开,讲清楚原理、写法和踩坑点。
2. color命令:五分钟上手整窗换肤
2.1 颜色参数与16色调色板
color命令的格式简单到不能再简单:
color 背景色前景色这里背景色和前景色都是十六进制数字字符,取值范围0到F。也就是说,命令后面跟两个字符就完成了全部设置。
| 字符 | 颜色 | 字符 | 颜色 |
|---|---|---|---|
| 0 | 黑色 | 8 | 灰色 |
| 1 | 蓝色 | 9 | 亮蓝色 |
| 2 | 绿色 | A | 亮绿色 |
| 3 | 湖蓝色 | B | 亮湖蓝色 |
| 4 | 红色 | C | 亮红色 |
| 5 | 紫色 | D | 亮紫色 |
| 6 | 黄色 | E | 亮黄色 |
| 7 | 白色 | F | 亮白色 |
举个例子,你想要经典的白字配蓝底,就是color 1F;想要绿字黑底,就是color 0A;想要亮红字黄底,就是color 6C。我把这个命令放在批处理开头,整个脚本执行期间窗口都保持这个配色,效果立竿见影。
注意:
color命令后面不带参数直接运行,会把当前cmd窗口恢复成默认的黑底白字。这在脚本收尾阶段很有用,避免你的工具“污染”用户原本设置的终端风格。
2.2 别踩这两个坑:颜色设置偶尔不生效、输出记录偏色
第一,有些程序会自己“覆盖”颜色。比如你在脚本里设置了color 0A,然后调用某个第三方工具或Python脚本,对方如果自己也输出ANSI转义序列,颜色就会被临时改变。这不是color命令失灵,而是内容级的染色优先级更高。你需要在调用完外部程序后,再补一次color命令来恢复整体配色。
第二,当输出被重定向到文件时,color只是改变cmd窗口的颜色,不会给文件内容嵌入任何颜色标记。你如果做color 0A > result.txt,那文件里只会留下一个空行,颜色并不会被“记录”进去。这种场景下想保留颜色信息,只能靠下一节的ANSI转义序列,或者换用支持颜色输出的日志框架。
3. ANSI转义序列:实现真正的局部彩色
3.1 Windows为什么以前不支持ANSI
很多人第一次接触ANSI转义序列是在Linux终端里,觉得那是Unix系的事。其实Windows最早期的命令提示符也考虑过支持,但后来图形化界面兴起,微软很长时间都没有把终端的控制台规范对齐到Linux标准。导致的结果就是:echo \033[31m红色\033[0m这类写法在Linux终端好使,在win7及更老版本的cmd里只会原样输出一串乱码。
Windows 10开始,微软重新实现了控制台宿主,底层引入了对ANSI转义序列的支持。不过有一个前提:需要开启“虚拟终端处理”模式。正常情况下这个模式是默认开启的,但某些精简版系统、远程桌面环境或特定的终端宿主设置,会把它关掉。如果发现ANSI序列不生效,大概率就是这里出了问题。
打开方式很简单,管理员权限打开cmd,执行:
REG ADD HKCU\CONSOLE /v VirtualTerminalLevel /t REG_DWORD /d 1 /f然后重启cmd窗口。这里的VirtualTerminalLevel值为1,表示开启VT处理;值为0则是关闭。批处理脚本里可以用reg query先检查当前值,再决定要不要写入。
3.2 转义序列语法与效果对照
ANSI转义序列虽然听着高端,拆开看其实就是一串特殊字符:ESC +[+ 控制参数 + 字母。ESC对应ASCII码27,在文档里经常写成\x1b或者\033。在cmd窗口里,一段完整的红色文字长这样:
echo ESC[31m这段文字是红色ESC[0m这里的31m表示设置前景色为红色,0m表示重置所有样式为默认。实际输入时,上面写的“ESC”并不是字母E、S、C,而是真正的ESC转义字符。在批处理文件里写入这个字符有特定办法,我后面专门讲。
常用的SGR参数我整理成了表格,对照着用就好。
| 代码 | 效果 | 代码 | 效果 |
|---|---|---|---|
| 0 | 重置所有样式 | 90-97 | 亮色前景色(黑红绿黄蓝紫青白) |
| 1 | 加粗 | 100-107 | 亮色背景色 |
| 4 | 下划线 | 30-37 | 标准前景色(黑红绿黄蓝紫青白) |
| 7 | 反显(前景背景互换) | 40-47 | 标准背景色 |
组合使用并不复杂,比如想要黄色背景、红色文字并加粗,可以写:
echo ESC[1;31;43m加粗红字黄底ESC[0m多个参数用分号分隔,顺序不影响结果,0m重置放最后就行。
3.3 在批处理中写入真正的ESC字符
这里是个大坑,新手基本都会卡住。如果你直接在bat文件里打ESC[31m,那里面其实是E、S、C三个字母,cmd把它当成普通文本输出,不是转义序列。你要插入的是ASCII码27对应的那个不可见字符。
我常用的办法有三个:
方法一:记事本软键盘。在记事本里,按住Alt键不放,在小键盘输入27,松开Alt,记事本里会插入一个不可见字符,这就是ESC。复制进你的批处理文件就行。注意必须用小键盘数字,笔记本需要先开NumLock。
方法二:先用PowerShell生成一个带ESC字符的脚本片段,再复制到bat里。PowerShell里执行:
$esc = [char]27 Set-Content -Path .\sample.bat -Value "echo ${esc}[31m红色${esc}[0m" -Encoding Default这段代码会生成一个bat文件,里面自动包含了真实的ESC字符。
方法三:写一个独立的“输出工具”,用C或Python生成带颜色的文本再让bat调用。这个方法最省心,适合脚本里颜色特别多的情况。
提示:如果你用某些编辑器(比如Notepad++、VS Code)编辑bat,往往可以通过“插入Unicode控制字符”或直接粘贴
\x1b的转义表示来插入。但超老实的Windows记事本不管显示与否,复制粘贴都不会丢这个字符,实测下来最稳。
3.4 256色与RGB真彩色:cmd也能玩出花
ANSI转义序列不止16色这一档。现代终端基本都支持256色调色板和24位RGB真彩色。cmd在Windows 10较新版本中也兼容这两套规范。
256色的前景色格式是:
echo ESC[38;5;编号m颜色文字ESC[0m编号范围0到255,其中0到15就是上面提到的16种标准色,16到231是6×6×6的彩色立方体,232到255是灰度梯度。背景色则把38改成48。
RGB真彩色格式是:
echo ESC[38;2;红;绿;蓝m颜色文字ESC[0m红、绿、蓝三个通道数值范围0到255。比如想要一个接近橙色的文本,可以设置38;2;255;165;0。这一下就把cmd终端的色彩上限拉到了和现代编辑器同一个水平。
不过提醒一句:256色和RGB真彩色效果跟cmd窗口使用的字体和渲染方式有关,老旧的“点阵字体”对色彩支持较差,建议把窗口字体换成“Consolas”或者“新宋体”,渲染效果会好很多。
4. 批处理里的实战封装
4.1 做一个返回值式的颜色输出函数
在批处理里面,如果要反复输出不同颜色的文字,最讨厌的就是每个echo后面都要跟一长串转义序列。我习惯把颜色输出包装成一个函数,放到脚本开头,后面调用就清爽多了。
@echo off chcp 65001 >nul call :EnableAnsi call :Cout "这是一条正常信息" "0" call :Cout "这是一条成功提示" "A" call :Cout "这是一条警告信息" "E" call :Cout "这是一条错误信息" "C" pause exit /b :EnableAnsi for /f "tokens=2 delims=." %%i in ('ver') do set "winver=%%i" reg query HKCU\CONSOLE /v VirtualTerminalLevel >nul 2>nul if errorlevel 1 ( reg add HKCU\CONSOLE /v VirtualTerminalLevel /t REG_DWORD /d 1 /f >nul ) exit /b :Cout set "msg=%~1" set "color=%~2" echo %ESC%[3%color%m%msg%%ESC%[0m exit /b这个封装里%ESC%是一个提前定义好的环境变量,存放ESC字符。实际运行前需要先用前面讲的办法把ESC字符插入到变量定义行。有了这个函数,后面的所有输出都变成一行调用,既清爽又能统一管理颜色。
4.2 日志分级:错误、警告、正常一眼看清
运维场景里,脚本执行时会产生大量日志,最常见的是把不同级别的内容染不同颜色。我一般在批处理工具里做两层处理:窗口输出实时染色,同时把不带颜色的原始文本写入日志文件。
核心思路是定义一个log函数,它接受级别和消息两个参数。错误用亮红色,警告用黄色,成功用绿色,普通信息用默认白色。代码可以这样写:
set "LOG_FILE=%~dp0run.log" call :Log "INFO" "开始执行备份任务" call :Log "OK" "数据库备份完成" call :Log "WARN" "磁盘剩余空间不足" call :Log "ERROR" "网络连接超时,重试失败" :Log set "level=%~1" set "msg=%~2" for /f "tokens=1-2 delims= " %%a in ("%date% %time%") do set "timestamp=%%a %%b" if /i "%level%"=="ERROR" ( echo %ESC%[91m[%timestamp%] [ERROR] %msg%%ESC%[0m ) else if /i "%level%"=="WARN" ( echo %ESC%[93m[%timestamp%] [WARN] %msg%%ESC%[0m ) else if /i "%level%"=="OK" ( echo %ESC%[92m[%timestamp%] [OK] %msg%%ESC%[0m ) else ( echo [%timestamp%] [INFO] %msg% ) echo [%timestamp%] [%level%] %msg%>> "%LOG_FILE%" exit /b这个函数的好处是,日志文件里是干净纯文本,任何编辑器都能打开;cmd窗口里则是彩色的分级信息,一眼就能看到错误在哪一行。
注意:往日志文件写内容时,一定要写未染色的原始文本。你要是把转义序列也写进文件,以后用
type或者记事本查看时,文件里会莫名多出一堆[31m之类的字符,很影响后续分析。
4.3 交互式菜单:给选项加上视觉引导
交互菜单是cmd工具最容易让人犯迷糊的地方。选项一多,用户不知道该看哪里。用ANSI转义序列把当前选项高亮出来,或者把所有选项按不同色块区分,交互体验会好很多。
我之前写过一个批量部署工具,菜单长这样:
echo %ESC%[36m======================================%ESC%[0m echo %ESC%[33m 请选择要执行的操作:%ESC%[0m echo %ESC%[32m [1] 启动服务%ESC%[0m echo %ESC%[32m [2] 停止服务%ESC%[0m echo %ESC%[33m [3] 重启服务%ESC%[0m echo %ESC%[31m [4] 退出%ESC%[0m echo %ESC%[36m======================================%ESC%[0m用户反馈说,有了颜色之后,一眼就能分辨操作区和警示区,不容易误选。这个小改动成本极低,但对工具专业度的影响很大。
4.4 动态进度条:用颜色区分状态
批处理里显示进度,常见做法是重复echo加退格符,配合颜色就能做出彩色状态条。虽然cmd不支持像Python那样灵活的光标控制,但通过set /p配合\r回车符,也能实现同一行刷新效果。
下面是一个简化版:每当文件拷贝完成,就输出一行绿色“OK”;出现失败时输出红色“FAIL”。配合延时,看起来就像实时刷新一样。操作的时候留意别在关键业务逻辑里加太多ping 127.0.0.1 -n 1 >nul这种延时,否则拖慢整体速度。
5. 在其它语言中给cmd输出上色
5.1 Python脚本输出彩色文本
很多人的工作流是:用Python写工具,然后在cmd窗口里运行。默认情况下,Python的print函数不识别颜色转义序列,你直接print("\033[31m红色\033[0m"),在部分环境里会原样输出乱码。但实际上,Windows 10之后的Python在启动时会自动调用控制台API,让转义序列生效。前提是你用print输出的字符串确实包含真正的ESC字符,而不是字面上的\033。
最省心的办法是安装colorama库,它底层会自动处理Windows和Unix的差异:
pip install colorama然后写:
from colorama import init, Fore, Back, Style init() print(f"{Fore.RED}错误:{Style.RESET_ALL}连接超时") print(f"{Fore.GREEN}成功:{Style.RESET_ALL}备份完成") print(f"{Fore.YELLOW}{Back.BLUE}警告:{Style.RESET_ALL}磁盘空间不足")init()会检测当前环境,如果是cmd窗口就启用ANSI支持,如果是重定向或管道环境,可以传convert=True参数,colorama会把颜色标记剥离,避免日志混乱。这段代码在cmd里运行,输出就是标准的红绿黄彩色文本。
如果不打算装第三方库,也可以直接用系统API:
import os os.system("") # 空命令,仅让Windows启用ANSI支持 print("\033[31m红色\033[0m")实测下来,os.system("")这行会在某些Windows环境中生效,但在精简版或老系统上不稳定,没有colorama来得可靠。
5.2 C/C++程序控制cmd颜色
C/C++程序要控制cmd颜色,除了输出ANSI转义序列,还有一条更经典的路:Windows控制台API,函数叫SetConsoleTextAttribute。它直接修改当前控制台的文本属性,不需要依赖终端是否支持ANSI,兼容性更好。
#include <windows.h> #include <iostream> int main() { HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE); SetConsoleTextAttribute(hOut, FOREGROUND_RED | FOREGROUND_INTENSITY); std::cout << "这是亮红色文字\n"; SetConsoleTextAttribute(hOut, FOREGROUND_GREEN | FOREGROUND_BLUE); std::cout << "这是青色文字\n"; SetConsoleTextAttribute(hOut, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE); std::cout << "恢复默认颜色\n"; return 0; }SetConsoleTextAttribute的第一个参数是控制台标准输出句柄,第二个参数是颜色属性。颜色属性低四位是前景色,高四位是背景色,可以用|把多个FOREGROUND_*常量组合起来。这个函数的特点是不需要关心终端对ANSI的支持,从XP到Windows 11都好使。
C/C++项目里如果既有跨平台需求,又不想写两套代码,我建议把ANSI转义序列作为主方案,在Windows启动时调用一次SetConsoleMode开启虚拟终端处理。这样代码里可以统一用转义序列,平台差异由一个初始化函数解决。这种写法在VS Code的终端里效果尤其好。
6. 常见问题与排查技巧实录
6.1 颜色不生效,出来一串乱码
最典型的现象是:echo输出了类似[31m红色[0m的字符,但颜色没变。原因基本可以锁定为ESC字符没有正确插入,终端没有收到真正的转义序列,把它当成了普通文本。
排查步骤我建议按顺序来:
- 先用十六进制查看bat文件的ESC位置字符,如果看到的是
1B,说明插入正确;看到的是45 53 43,那就是三个字母,插入方式错了。 - 再检查系统版本,Windows 10以下基本不支持ANSI序列,建议用
color命令或者C/C++的API。 - 检查注册表
HKCU\CONSOLE\VirtualTerminalLevel是否为1,不是就改成1再重启cmd。 - 最后排除第三方终端软件干扰,Windows Terminal、ConEmu、Cmder对ANSI支持情况各有不同,但通常都支持;实在不行就在原生cmd窗口里试。
6.2 重定向、管道、日志记录时颜色丢失
把彩色输出重定向到文件,文件里要么什么都没有,要么出现一堆乱码。这个问题的根因是:ANSI转义序列只在终端显示时有意义,写入文件时它只是普通的字节序列。color命令重定向到文件后没有内容,转义序列重定向到文件后则会产生[31m这类杂质。
解决方案很简单:不要让日志文件和终端输出共用同一个输出函数。作为个人脚本,可以在颜色函数里写两行,一行输出彩色到屏幕,一行写纯文本到文件。前面我给的log函数就是这种模式。如果你在写Python脚本,还可以配合logging模块的StreamHandler,在控制台用彩色Formatter,同时让FileHandler使用纯文本Formatter,两者互不干扰。
6.3 中文输出变乱码,和颜色标记混在一起
cmd默认代码页是GBK(936),如果你在批处理里用了UTF-8编码,中文会显示成乱码;如果你在脚本里执行了chcp 65001切换成UTF-8,那输出里如果混入非UTF-8字符又会乱。这跟颜色本身没关系,但颜色序列夹在中文中间时,字符边界一乱,整行就废了。
我的经验是:bat文件尽量用系统默认的GBK编码保存,或者在文件开头统一执行一次chcp 65001 >nul,同时保证bat文件本身以UTF-8保存。二者必须匹配,不然必出乱码。推荐用VS Code或者Notepad++,保存时能明确看到当前编码格式。
6.4 修改用户名或拖拽文件到cmd无效,顺带说两句
有网友问为什么改了Windows用户名后,cmd窗口路径仍显示旧用户名。这是因为cmd窗口的默认%USERPROFILE%变量和实际Shell路径是在进程启动时初始化的,修改系统用户名不会自动更新当前运行的cmd实例,重启终端就好了。还有一种情况是你改了用户目录的文件夹名,但没有同步修改注册表里的ProfileImagePath,这个需要专门处理,不在颜色讨论范围内。
另外Windows 11某个版本里,拖拽文件到cmd窗口会出现“目标路径无效”之类的提示。这个问题本质上跟颜色输出无关,大概率是系统更新后默认终端改成了Windows Terminal,cmd的拖拽处理逻辑有变化。解决办法是,把Windows Terminal的defaultProfile改成cmd,或直接在Windows Terminal的标签页里拖文件。
6.5 读写权限和脚本执行策略
如果你在C盘系统目录下操作cmd,偶尔会遇到写注册表或创建临时文件失败的情况。这跟颜色输出没直接关系,但会让脚本在调用reg add开启ANSI支持时卡住。解决办法是:避开C盘根目录和C:\Windows\System32,把批处理和日志文件统一放在用户目录或D盘工作目录;如果必须改注册表,就用管理员权限运行cmd,或者提前用icacls调整目录权限。
我自己经常把脚本放到D:\scripts这种独立工作区,颜色函数、日志输出、临时文件都围绕这个目录组织,省了很多权限问题。
7. 顺着这条路,还能看那些相关用法
把颜色玩熟之后,很多之前觉得“没必要”的cmd功能都会顺带打开。比如用chcp切换代码页时配合颜色区分编码状态,用ping测试端口时把成功失败输出染成不同颜色,甚至把扫盘命令的结果按剩余空间量级着色,这些都可以在一个脚本里统一起来。cmd最大的特点就是轻量、随叫随到,配合ANSI序列以后,它就不再是那个“又黑又土”的裸终端了。
我个人在实际操作中的体会是:颜色输出这事,千万不要贪多。脚本里所有文字都花花绿绿,反而失去了重点。把错误印成红色,把成功印成绿色,把警告印成黄色,这三条用足,就已经能覆盖90%的场景。再加一个“重置所有样式”的兜底习惯,任何外部程序调用后都把颜色恢复回来,你的cmd脚本就足够专业和稳了。最后再分享一个小技巧:给常用脚本起一个统一的彩色启动横幅,把脚本名称、版本号、作者用青色打印出来,不仅好看,别人拿到你的工具时也会觉得你考虑得很周到。