跳到主要内容
ansicode

无障碍与 NO_COLOR —— 关闭颜色、照顾屏幕阅读器

每个 CLI 工具默认输出彩色;并非每位用户都想要、也非每种环境都能渲染。本页面覆盖七种协作信号 —— NO_COLOR(现代事实标准的总开关)、CLICOLOR / CLICOLOR_FORCE(BSD 系成对开关)、FORCE_COLOR(Node 系带颜色深度的覆写)、TERM=dumb(通用「无能力」标记)、按流分别判断的 isatty(stdout 与 stderr 独立)、COLORFGBG(终端背景提示),以及屏幕阅读器实际如何朗读彩色输出 —— 它们共同允许用户与工具协商何时发送 SGR 字节、何时保持安静。

NO_COLOR

NO_COLOR —— 事实标准的颜色总开关

由 no-color.org 定义,被 ripgrep、fd、bat、eza、jq、npm、pip、cargo、gh、kubectl、docker、terraform 与大多数现代 CLI 采纳。规则很简单:若环境变量 NO_COLOR 被设为任何非空值,工具必须关闭彩色输出。值本身被忽略 —— 只看是否存在。NO_COLOR 应该是程序的第一道检查,先于任何 TTY 检测 —— 因为低视力、单色终端、自定义调色板下你的颜色显得难看的用户、以及屏幕阅读器用户都通过它显式退出。

只判断存在与否(或值为 '1'),不要去比较具体值。

# In bash / any POSIX shell
export NO_COLOR=1

# Disable for a single command
NO_COLOR=1 git status

# In your tool (POSIX):
if [ -n "$NO_COLOR" ]; then USE_COLOR=0; fi
CLICOLOR / CLICOLOR_FORCE

CLICOLOR 与 CLICOLOR_FORCE —— BSD 系的成对开关

源自 BSD(macOS ls、FreeBSD grep),现亦被 git、ripgrep、eza、fd 采纳。两个互补变量:CLICOLOR=0 强制关闭颜色(语义与 NO_COLOR 类似但要比较值);CLICOLOR=1(或未设置)让工具按 stdout 是否为 TTY 决定;CLICOLOR_FORCE=1 强制开启颜色,即使 stdout 不是 TTY —— 通常用于把输出通过管道送往 less -R 或先写文件后在终端查看的场景。多数工具遵循的优先级:NO_COLOR > CLICOLOR_FORCE > CLICOLOR > isatty(stdout)。

向支持 ANSI 的分页器(如 less -R)传管道时,CLICOLOR_FORCE 是保留颜色的标准做法。

# Force colour through a pipe (BSD style)
CLICOLOR_FORCE=1 ls | less -R

# Disable explicitly
CLICOLOR=0 git diff
FORCE_COLOR

FORCE_COLOR —— Node.js 系的颜色深度约定

由 chalk 推广,被 Node.js 生态广泛采纳(npm、yarn、mocha、jest、eslint、prettier、vite、next、tsx)。与 NO_COLOR(布尔)和 CLICOLOR_FORCE(强制开启)不同,FORCE_COLOR 携带显式深度:0 = 关闭、1 = 16 色(基本 SGR)、2 = 256 色(xterm-256)、3 = 真彩色(24-bit RGB)。FORCE_COLOR=true 等价于 1。引入此变量是因为 npm 把输出通过 jq 管道后默认丢失颜色,维护者希望提供一个其他生态可选地遵循的统一覆写。许多非 Node 工具(ripgrep、fd)现在也尊重 FORCE_COLOR —— 即便它们出自 Rust。

FORCE_COLOR 编码的是期望的颜色深度,而非简单的开关。

# Force truecolour even when piped
FORCE_COLOR=3 npm test | tee build.log

# Force basic 16-colour fallback
FORCE_COLOR=1 jest --colors
TERM=dumb

TERM=dumb —— 通用的「无能力」信号

当 TERM 被设为 `dumb` 时,terminfo 返回最小能力集:无光标定位、无颜色、无清屏。Emacs 在其 shell-mode buffer 中使用 TERM=dumb,因为 Emacs 自己渲染输出而不让底层终端解释控制码。其他常见场景:许多 CI runner(GitHub Actions 默认 TERM=dumb,除非显式 export TERM=xterm-256color)、多数 cron 任务、部分 `tmux send-keys` 调用,以及任何无控制终端的进程。规范的 CLI 工具应在 NO_COLOR 之前先检查 `$TERM == 'dumb'` 作为颜色关闭信号 —— 把 SGR 字节发到 Emacs shell buffer 或 CI 日志里通常只会产生视觉噪音。

把 dumb 与 TERM 未设置一并处理 —— 两者失败方向相同。

# Defensive check in your CLI tool
case "$TERM" in
  dumb|'') USE_COLOR=0 ;;
  *) USE_COLOR=1 ;;
esac
isatty + stderr

isatty(stdout) 与 isatty(stderr) —— 两条流独立判断

stdout 与 stderr 是独立的文件描述符。`cmd 2>&1 | less` 把 stdout 变成管道(非 TTY),但 stderr 内部的检测逻辑并未改变。规范的工具应分别调用 `isatty(STDOUT_FILENO)` 决定是否给 stdout 上色、`isatty(STDERR_FILENO)` 决定是否给 stderr 上色。常见 bug:用一个从 stdout 算出的全局 `is_tty` 变量同时复用给 stderr —— 当用户 2> errors.log 重定向错误流时,彩色字节会泄漏到日志文件。NO_COLOR、CLICOLOR 一次读取即可,但 TTY 状态必须按流分别判断。

两次 TTY 检查、两个布尔值,每条写入流各一个。

// C
int stdout_tty = isatty(STDOUT_FILENO);
int stderr_tty = isatty(STDERR_FILENO);
int err_color  = stdout_tty || stderr_tty ? 0 : (stderr_tty);
// Decide per stream — never share a single boolean.
COLORFGBG

COLORFGBG —— 终端设置的前后景色提示

由许多终端模拟器设置(urxvt 首创,konsole、terminator、gnome-terminal、mintty、xterm(配置正确)、alacritty 通过 env 等纷纷采用)。格式:`FG;BG` 或 `FG;装饰;BG`,每个值是标准 16 色索引(0–15)—— 故 `15;0` 表示白底黑字、`0;15` 表示黑底白字。事实用途:需要根据用户实际背景选可读语法主题的工具读取 COLORFGBG,选择深色或浅色变体。`bat`、`mintty`、`vim` 的 `:set background=` 自动检测、`neovim` 的 `colorscheme` 自动检测都用它。相对 DECRQSS 实时查询(向终端直接问)可靠性略低,但无需 raw 模式与往返开销。

标准调色板中索引 ≥ 8 通常是「亮」色 —— 一个粗略的浅 vs 深的启发式判断。

# Crude light/dark detection in a shell script
bg="${COLORFGBG##*;}"
if [ -n "$bg" ] && [ "$bg" -ge 8 ]; then
  echo "likely light background"
else
  echo "likely dark background"
fi
屏幕阅读器

JAWS / NVDA / VoiceOver 如何朗读含转义码的输出

若用户依赖屏幕阅读器,`\x1b[31mERROR\x1b[0m` 会被怎么读?取决于周围软件是否预先剥离 ANSI。在尊重屏幕阅读器提示的终端模拟器(macOS Terminal + VoiceOver、Windows Terminal + Narrator)中,可见的「ERROR」被朗读 —— ESC 字节被终端的单元缓冲吸收,从未传到无障碍层。但在不那么友好的终端、或文本编辑器读取日志文件时,每一字节都会被字面朗读:「反斜杠 x 1 b 左方括号 3 1 m E R R O R 反斜杠 x 1 b 左方括号 0 m」。结论:产出用于事后查看的日志的工具,默认应尊重 NO_COLOR 才算无障碍;输出彩色的工具至少要确保每段都有 SGR 重置(`\x1b[0m`)收尾 —— 一段未关闭的样式延续进周围文字会让屏幕阅读器的噪音再翻四倍。

参见

/strip 提供事后剥离 ANSI 字节的正则;/pitfalls 覆盖这些问题的现象层版本;/terminals 显示哪些模拟器尊重各个信号;/use 列出各语言中替你读取 NO_COLOR 的库。