在终端中显示颜色


大多数终端仿真器对它们支持的颜色空间不确定。这就是为什么大多数检测库在使用平台和终端模拟器的特定组合时都支持硬代码颜色。

如果你在编写命令行工具你会希望通过颜色来增强输出的清晰度。表面上看起来很简单的事情,一旦你深入了解各种终端模拟器如何支持颜色,情况就会变得有些复杂。有趣的是,这种复杂性在很大程度上是由于历史原因,至今仍然存在。

细说从前

在计算的早期,没有终端颜色。所有内容都是以黑色或白色呈现的。对终端中更复杂渲染的需求增加了,这就是 ANSI 转义码的诞生。它们代表一组特殊的字符序列,控制光标位置、颜色、背景色、字体样式等选项。

1
2
// 这将把字符串 "foobar" 以绿色打印到终端
console.log("\u001b[32mfoobar\u001b[39m");

这在终端中看起来是这样的:

不过有一个问题,那就是只支持总共16种颜色。其中将近一半是现有颜色的较浅色调,所以实际感知到的颜色数量感觉更有限。尽管有这些限制,但它们通常足以满足大多数应用程序的需求。

关于这个颜色调色板的一个很酷的方面是几乎每个终端模拟器都允许你更改颜色值。这为根据你的喜好进行主题和样式设置打开了大门。你可以在 Wikipedia 上查看各种终端模拟器默认调色板的概述。

支持的所有颜色列表是:

  • black
  • white
  • gray + light gray
  • red + light red
  • green + light green
  • yellow + light yellow
  • blue + light blue
  • magenta + light magenta
  • cyan + light cyan

所有这些颜色都可以用作前景色或背景色。这是它们在我的终端中的渲染效果。

支持所有颜色

随着时间的推移,计算机不断进步,开发人员希望在终端中使用的颜色丰富性也随之增加。这导致增加了一个增强的 8 位颜色空间,支持多达 256 种颜色。突然间,不仅仅只有绿色和深绿色了。你现在可以显示各种绿色的色调了!

但为什么要止步于此呢?再过几年,随着显卡的引入,对更多颜色的需求增加了。应用程序渲染16位或24位颜色变得很常见。终端模拟器很快也跟上了步伐,直接跳到了24位颜色,通常称为“真彩色”支持。由于这个帖子篇幅有限,我就不贴截图了。只想说,从256种颜色跃升到1670万种颜色是一个很大的飞跃。

总而言之,我们最终有了4种不同的颜色空间:

  • black & white
  • Ansi, 16 colors
  • Ansi 256 colors
  • 24-bit True Color

检测颜色支持

这一部分变得复杂,因为每个终端模拟器的做法略有不同。没有标准化的方法来检测支持哪种颜色空间。这不仅仅是终端模拟器的问题,因为现在的开发人员还希望CI日志也有颜色。大多数环境根本不会告诉你它们支持哪种颜色空间。

最常见的检测颜色支持的方法是检查TERMCOLORTERM环境变量。CI系统可以通过检查CI环境变量的存在来检测。结合程序运行的操作系统信息,我们就有了一种足够好的颜色检测方法。

如果COLORTERM是24bit或truecolor,那么你可以确定支持24位真彩色。检测ANSI 256通常是通过检查$TERM是否以256或256color结尾。如果支持真彩色,则必定支持ANSI 256。对基本的ANSI转义码也是如此。再说一次,这种检测逻辑既不完美也不优雅,但在实际应用中相当可靠。另一方面,Windows终端不会给你任何提示。这两个环境变量都没有设置。因此,大家都简单地假设终端支持完整的24位颜色,自从Windows 10修订版14931以来确实如此。

名称 操作系统 ANSI ANSI 256 真彩色 $TERM $COLORTERM $CI
Terminal.app macOS - xterm-256color - -
iTerm macOS xterm-256color truecolor -
Windows Terminal Windows - - -
PowerShell Windows - - -
GitHub Actions Linux (Ubuntu) dumb - true

唯一不支持真彩色的常见终端模拟器是macOS内置的Terminal.app。它仅支持最多ANSI 256颜色。

CI系统是这里真正的终极挑战,因为它们通常将自己宣传为不支持颜色的哑终端。由于开发人员希望日志包含颜色,因此除了忽略TERMCOLORTERM变量之外别无选择。相反,通过检测代码是否在CI中运行来推断颜色支持。

颜色转换

在颜色方面,缺失的一环是将一种颜色空间转换为另一种。当开发人员使用真彩色表示法在终端上打印内容时,我们至少应该能够显示一些颜色。

如果你在不支持真彩色的终端模拟器中尝试渲染真彩色,会是这样的:

而将同样的颜色转换为更有限的ANSI 256颜色空间,则如下所示:

当然,颜色略有不同,但总比没有好。这是一种“足够好”的折衷,保持了原始意图。据我所知,这只适用于macOSTerminal.app

怎么做得更好吗?

尽管有了这些进步,作为开发人员仍然需要了解ANSI转义码感觉有点奇怪。大多数开发人员只是想设置文本颜色或背景颜色。你知道浏览器允许你通过纯CSS来样式化console.log消息。如果我们在服务器上利用同样的东西会怎样?这正是deno所做的。他们做对了。他们允许你使用相同的API在服务器上打印颜色。

1
2
3
4
5
6
console.log(
"%cThis text is lime green %cand %cthis text has a red background",
"color: #86efac",
"",
"background-color: red; color: white"
);

Chrome浏览器控制台中呈现:

在服务器上用deno执行相同代码:

结论

有时候,细节决定一切。颜色一直是使CLI输出更具可读性的一个重要部分。它们可以引入仅凭字符形状无法实现的额外视觉层次。它们可以引入额外的视觉层次,这是单纯的角色形状无法做到的。为一些项目固定颜色检测支持是一个有趣的小调查。幸运的是,生态系统中的现有库已经解决了大多数复杂性,因此您不必自己解决这个问题。