接手过批量脚本、或者给同事做自动化工具的人应该都经历过这种尴尬脚本跑得挺顺但屏幕上一水儿的灰白色文字成功、警告、错误全靠缩进和符号猜一个不小心就把ERROR看漏了。某次我试着在批处理里用echo输出不同前景色和背景色之后整个脚本的界面感立刻不一样了——错误红底白字、成功绿字、警告黄字状态一眼扫过去就清楚。这篇就把我折腾出来的完整思路写下来从 ANSI 转义原理、ESC 字符生成、颜色代码表到封装函数和做彩色菜单最后附上几个我踩过的坑想给自己的批处理加一层门面的同学可以直接照抄。1. 先把两件事分开全局配色的 color 命令与局部着色的 ANSI 转义很多朋友刚接触批处理颜色时第一反应是color命令。这没错但它和让某一行 echo 变成红色是两个完全不同的机制。搞混了就会出现改了 color 整个屏幕都变色的情况。1.1 color 命令到底能做什么不能做什么color命令的语法是color 背景色前景色两个字符都取 0-F 十六进制。比如color 0A就是黑底亮绿字color 4F是红底亮白字。它面向的是整个控制台窗口一旦执行窗口里已有的内容、后续输出的内容全部变成这套配色。它和echo之间没有任何绑定关系。color切的是控制台的默认前景色和背景色除非你再执行一次color或关闭窗口否则不会自动恢复。这种全局刷色的特性决定了它适合做脚本开头的主题切换或者结束前的整体重置但不适合让某一行文字单独与众不同。还有一点容易忽略color命令不接受 RGB 或更多的色彩配置16 色已经上限。真想在批处理里做出细腻的界面光靠它是做不到的。1.2 ANSI 转义序列的工作方式真正能让echo输出局部颜色的是ANSI 转义序列具体来说是其中的 SGRSelect Graphic Rendition序列。格式长这样ESC [ 属性 mESC是 ASCII 码 27 那个控制字符后面跟左方括号[然后是一个或多个用分号隔开的数字最后以m结束。控制台收到这串东西后会把它当作样式指令而不是可见文字立刻切换之后的输出样式。比如ESC[31m表示从现在开始用红色前景ESC[0m表示恢复默认样式。这玩意儿是有状态的你输入一次后续所有 echo 都保持这个颜色直到再收到新的 SGR 序列。所以实际使用中每条彩色输出后面几乎都要跟一个ESC[0m否则后面全被带跑。之所以能实现这一行红、下一行绿就是因为这种状态只影响转义序列出现之后的内容不像color那样影响整个窗口。控制台窗口本身是黑是白则是另一套逻辑两者可以共存配合好了就会很舒服。1.3 先说结论能不能直接用的判断标准不少人的批处理在 Windows 7 上跑了十年突然拿到 Windows 10 或 11 的机器上却发现颜色出不来了——其实方向反了。新版系统自带的控制台宿主默认支持 ANSI 转义序列老系统的传统控制台则不支持会把ESC[31m当成一堆乱码原样打出来。判断标准很简单Windows 10 较新版本、Windows 11 自带的控制台支持直接用。Windows Terminal、VS Code 内置终端这类现代终端宿主支持体验还更好。Windows 7 时代的传统 cmd 窗口基本不支持需要其他曲线方案。脚本输出被重定向到文本文件或用管道传给别的命令转义序列不会生效会变成字符写进文件或流里。我现在的做法是脚本开头先检测系统版本或终端特性不支持就自动降级成纯文字加符号标记。这部分放到后面第 5 节细说先把能跑通的路走完。2. 把一个看不见的 ESC 字符塞进批处理三种可行方案批处理文件里没法直接敲一个ESC键出来但你需要真正 0x1B 那个字节。这里给三种我实测过的方式按场景选就行。2.1 经典型做法prompt $E 配合 for /f这是圈里流传很广的经典写法核心是利用cmd的prompt命令对$E的解释。prompt $E会把命令提示符设成 ESC 字符然后用for /f把这个字符捕获到变量里。echo off setlocal EnableDelayedExpansion for /f delims %%e in (echo prompt $E ^| cmd) do set ESC%%e echo !ESC![31m 我是红色 !ESC![0m拆开解释一下这条for /fecho prompt $E ^| cmd在for /f的子进程里执行^|是为了把管道符转义让外层命令正确解析。echo prompt $E把字符串prompt $E传进管道由子cmd进程执行。子cmd执行prompt $E后它的提示符就变成了 ESC 字符for /f再把提示符内容抓回来存到ESC变量。这个方案的好处是纯 cmd 实现不依赖额外运行环境脚本启动速度没有明显损耗。缺点是理解起来有点绕而且个别老版本控制台环境下抓取结果可能带额外字符需要在不同机器上验证。我在很多生产脚本里都用它稳定性足够。2.2 借用 PowerShell 生成现代系统更省心的选择如果你的脚本运行环境确定有 PowerShell可以用更直白的方式echo off setlocal EnableDelayedExpansion for /f delims %%e in (powershell -noprofile -command [char]27) do set ESC%%e echo !ESC![32m 我是绿色 !ESC![0m[char]27在 PowerShell 里就是十进制 27 对应的字符也就是 ESC。for /f会把它作为一行输出捕获。与经典方案相比这行代码的意义几乎不需要解释谁都能看懂。代价是每次都要拉起一个 PowerShell 进程虽然只运行一次影响不大但在配置很差的机器上启动时间会有可感知的延迟。我通常把两套方案写在一起通过判断PowerShell是否存在来切换。2.3 直接把 ESC 字符写进脚本文件这是最笨但最直观的方法用支持插入特殊字符的编辑器在你的批处理文件里直接放一个 ESC 字符。文件保存后那个位置就是一个真实的 0x1B 字节echo输出时控制台能直接识别。比如你可以在脚本里这样写echo off echo ←[33m 我是黄色 !←[0m上面那行中←的位置实际是 ESC 字符0x1B只是在很多编辑器里显示不出来或显示成特殊符号。用这个方法的好处是最终文件自包含不需要任何运行时生成坏处是一旦文件编码被改、编辑器不支持、复制粘贴过程中丢了字符问题很难发现。而且不同编辑器对不可见字符处理差异大发布给别人用容易被误伤。我个人认为这个方案适合自己本机一次性脚本不适合往团队里分发。2.4 生成后如何快速验证不管用哪种方式拿到ESC变量先做一个最简单的视觉测试echo %ESC%[31m红色%ESC%[0m echo %ESC%[44;97m蓝底白字%ESC%[0m如果第一行显示红色、第二行蓝底白字说明 ESC 生成正确。如果你看到的是一堆[31m之类的文字基本可以断定 ESC 变量是空的或者控制台不支持 ANSI。另外提醒一句不要在set输出里检查 ESC 是否存在控制字符在set显示时经常看不见。直接用颜色输出验证最省事。3. echo 前景色与背景色代码全表从 16 色到 24 位真彩色颜色代码本身不复杂但很多人是边用边查搞不清数字含义。我把常用的全列出来按三个维度拆开讲。3.1 基础 8 色前景 30-37背景 40-47规则非常好记前景色是 30 开头背景色是 40 开头。下面这张表覆盖了最常用的基础色。颜色前景代码背景代码效果示例黑3040ESC[30m黑字红3141ESC[31m红字绿3242ESC[32m绿字黄3343ESC[33m黄字蓝3444ESC[34m蓝字品红3545ESC[35m品红字青3646ESC[36m青字白3747ESC[37m白字多个属性用分号合并比如ESC[31;44m是红字蓝底ESC[33;41m是黄字红底。注意顺序无关紧要只要数字和分号正确就行。3.2 亮色 8 色前景 90-97背景 100-107基础 8 色偏暗尤其在黑色背景下暗红暗蓝看起来很吃力。亮色系列把前景换成 90-97背景换成 100-107对应同样的 8 种颜色但更鲜亮。echo %ESC%[92m亮绿%ESC%[0m echo %ESC%[91;107m亮红字亮白底%ESC%[0m我习惯把成功信息用亮绿、错误用亮红实测在默认黑色背景里对比度比基础色舒服很多。如果屏幕偏亮也可以反过来用暗色。3.3 文本属性加粗、下划线、反显怎么加除了颜色SGR 还有几个常用属性1加粗有些终端只是变亮4下划线7反显前景背景互换0全部重置可以叠加使用echo %ESC%[1;4;31m加粗下划线红字%ESC%[0m echo %ESC%[7;36m反显青字%ESC%[0m这里有个细节很多终端对加粗某种颜色的处理是显示为亮色而不是真正的粗体所以别指望它像文档里那样笔画变粗。反显倒是很实用做菜单选中的高亮项时用7把前景背景互换一下比手动拼背景色省事。3.4 256 色与真彩色批处理界面的高级素材觉得 16 色不够用可以上 256 色和真彩色256 色前景ESC[38;5;N m背景ESC[48;5;N m其中 N 取 0-255。真彩色前景ESC[38;2;R;G;B m背景ESC[48;2;R;G;B m。echo %ESC%[38;5;208m256色橙字%ESC%[0m echo %ESC%[48;2;30;60;90m真彩色蓝底%ESC%[0m256 色的 0-15 对应前面说的标准色16-231 是 6x6x6 色立方体232-255 是灰度。真彩色则是直接给 RGB 三个通道值理论上能显示任意颜色。注意256 色和真彩色的支持程度取决于终端宿主老控制台可能不支持。我的经验是批处理界面追求的是稳定清晰的提示16 色基本足够256 色用在进度条渐变上会有惊喜但别把关键信息押在高级色上。3.4 重置与嵌套颜色不串台的规则ESC[0m负责重置一切让你后面输出的内容回到默认样式。完整写法是开头设置颜色ESC[31;1m中间输出文本结尾立刻ESC[0m重置是最容易被新手漏掉的。漏掉一次后面几十行全变红然后整片脚本的样式就崩了。我做封装时会把设置颜色和重置放在同一个调用里尽量不让裸的转义序列散落在业务代码中。4. 把转义序列变成真能用的界面变量封装、状态输出与彩色菜单裸的转义码在脚本里到处都是不仅难读还容易出错。我习惯先封装再使用这样脚本主体看起来干干净净。4.1 第一步用变量把颜色码收拢起来echo off setlocal EnableDelayedExpansion for /f delims %%e in (echo prompt $E ^| cmd) do set ESC%%e set RED%ESC%[31m set GREEN%ESC%[32m set YELLOW%ESC%[33m set BLUE%ESC%[34m set BOLD%ESC%[1m set RESET%ESC%[0m echo !RED!错误文件不存在!RESET! echo !GREEN!成功任务完成!RESET!把颜色码定义为变量之后业务代码里只有!RED!、!GREEN!这种语义清晰的词后期要统一换色只改变量定义那一段就行。这里我建议用延迟展开的!变量!语法而不是%变量%因为脚本里经常会有括号包裹的逻辑块普通展开很容易在解析阶段就出问题。4.2 第二步封装一个带重置的颜色输出函数比变量更进一步的是定义一个函数式写法把颜色 文本 重置绑在一起:print_color rem %1颜色码 %2文本 echo %~1%~2%ESC%[0m exit /b 0调用时这样call :print_color %RED% 正在删除临时文件... call :print_color %YELLOW% 注意磁盘剩余空间不足注意我传的是整个颜色变量函数里%~1展开后就是ESC[31m这种序列后面的文本紧接着输出最后固定补一个重置。这样调用点不会漏掉重置也方便统一修改输出格式。不过call有进程开销循环里频繁调用会拖慢速度。如果只是打印几百行日志完全不用在意如果是上万的循环建议只在循环体外封装循环体内直接用变量拼好的一次性格式串。4.3 第三步做一个带背景色的选择菜单框架当颜色变量和输出函数都就位就能做点像样的界面。下面是典型的选择菜单echo off setlocal EnableDelayedExpansion for /f delims %%e in (echo prompt $E ^| cmd) do set ESC%%e set CYAN%ESC%[36m set WHITE_ON_BLUE%ESC%[44;97m set YELLOW%ESC%[33m set RESET%ESC%[0m call :menu_title 批处理工具箱 call :menu_item 1 备份当前目录 call :menu_item 2 清理临时文件 call :menu_item 3 退出 :menu_title echo. echo !WHITE_ON_BLUE! %~1 !RESET! echo. exit /b 0 :menu_item echo !CYAN![%~1]!RESET! %~2 exit /b 0menu_title用白字蓝底把标题区突出出来menu_item用青色把编号高亮选项文字保持默认色。整个菜单一眼就能区分结构和内容。如果想让默认选中的项反白就在那一行的开头加ESC[7m结尾加ESC[0m。4.4 进阶进度条与分区标题的视觉方案进度条是另一个能明显提升界面感的点。不需要复杂的刷新用行内覆盖就行setlocal EnableDelayedExpansion for /l %%i in (1,1,10) do ( set /a done%%i*10 set bar for /l %%j in (1,1,%%i) do set bar!bar!# nul set /p %ESC%[32m[!bar!] !done!%%%ESC%[0m timeout /t 1 /nobreak nul ) echo.核心是set /p 文本不换行再用#字符累计出进度条。%%在批处理里写百分比要翻倍。分区标题则可以用一整行背景色撑出宽度比如echo %ESC%[44;97m%ESC%[0m echo %ESC%[44;97m 部署配置检查%ESC%[0m把背景色和装饰字符组合视觉上比单纯彩色文字更有界面的形态感。5. 颜色不生效、显示乱码、颜色串台三类常见问题的完整排查链路折腾彩色批处理十次里有八次会遇到问题。大部分不是代码写错而是对转义字符和终端环境的理解没到位。我按症状说排查顺序。5.1 输出一串[31m这种乱码问题在哪症状很典型屏幕上出现[31m红色[0m这样的可见文字[和m都打出来了。排查链路从最外层开始先确认 ESC 变量里真的有字符。在脚本里临时加一行echo %ESC%用支持显示控制字符的编辑器看输出文件或者直接用一个已知能显示颜色的终端测试。确认终端是否支持 ANSI。在 cmd 里手动执行echo加一个粘贴进来的转义序列如果手动都不行就是终端问题换新式终端或降级方案。检查文件编码。批处理文件若被存成带 BOM 的 UTF-8第一行可能出现不可见字符导致echo off失效命令本身被打乱。建议用 ANSI 或 UTF-8 无 BOM。多数情况下问题出在第 2 步老控制台不支持或者脚本跑在某个不支持转义的内嵌终端里。5.2 界面中途颜色乱掉重置与 color 命令的冲突你前面的颜色都正常突然从某一处开始所有输出都变一个色或者整屏背景被刷掉了。这种通常是两件事某次echo设置了前景或背景色但后续没有重置。漏一个ESC[0m后面全废。排查时搜索所有设置颜色的行看它们对应的重置在哪里。脚本中途执行了color命令比如为了临时改背景调用了color 1F。color改变的是整个控制台默认配色它会覆盖掉 ANSI 状态导致之前的样式约定全部失效。我现在的规范是color只允许出现在脚本最开始设置主题和最后恢复默认其余地方一律用 ANSI 序列的局部样式。如果要恢复控制台默认色用color不带参数。5.3 变量值被吞或延迟展开导致颜色失效有时候颜色不是不生效而是变量里的 ESC 被提前处理了。典型的场景是if %choice%1 ( echo %RED%执行中%RESET% )这段在if括号块里用%RED%cmd 在解析整个块时就已经把变量替换成字符串了。如果在此之前的某条命令改变了RED的值块内拿到的还是旧值更麻烦的是某些特殊字符在语法解析阶段就可能引发问题。解决办法统一是启用延迟展开并在块内用!RED!setlocal EnableDelayedExpansion if %choice%1 ( echo !RED!执行中!RESET! )还有个小坑set ESC把变量清空后如果某行还是%ESC%展开结果就是空字符串后面的[31m会变成可见文本。所以清空和重新生成 ESC 的时机也要统一。5.4 与日志重定向、管道输出的交互注意事项最后一个坑最容易让人抓狂屏幕显示正常但script.bat log.txt以后日志里全是[31m这样的代码。这不是故障是设计使然。转义序列只有终端才解释重定向成文件后它就是普通字节原样保留。所以如果脚本要同时兼顾屏幕彩色和日志干净得加一个判断if defined NO_COLOR ( echo 错误文件不存在 ) else ( echo !RED!错误文件不存在RESET! )或者专门加个参数开关默认开颜色接管道或重定向时用--no-color关掉颜色。这也是很多命令行工具的做法批处理里自己写一遍也不复杂。我在实际使用中发现做批处理彩色界面最值钱的不是那几行颜色代码而是一开始就想清楚哪些信息用哪种颜色、颜色开关怎么传、重置在哪里。把这些规范定好后面在脚本里加任何输出心里都不慌。
阅读完成 · 觉得有帮助?