终端的颜色控制

简介

终端的颜色控制在两个标准中有定义。一个是ISO/IEC 6429:1992,另一个是ECMA-48。前者的最新版本是Edition 3, 1992,后者的最新版本是5th edition, June 1991

有趣的是,ISO和ECMA的总部都在日内瓦,而IEC和ITU也是。

这两个标准中关于颜色的定义部分为SGR (SELECT GRAPHIC RENDITION)。在 ISO/IEC 6429:1992 中的章节为 8.3.118 SGR - SELECT GRAPHIC RENDITION;在ECMA-48中的章节为:8.3.117 SGR - SELECT GRAPHIC RENDITION。前者是收费的,后者可以从这里下载。

两个标准关于SGR的定义是一致的。在下面的标准引用中,如无特殊说明,均为ECMA。

SGR标准是跨平台的终端控制协议,它既不是操作系统(如 Linux)的组成部分,也不是命令解释器(如 Bash)的功能。尽管本文中的实验是基于Linux Bash的。这也是本文的题目不是"Linux的颜色控制"或"Bash的颜色控制"的原因。

SGR 的格式

在ECMA中,关于SGR的说明如下:

复制代码
Notation: (Ps...)
Representation: CSI Ps... 06/13
Parameter default value: Ps = 0
# 下面是参数表,此处省略
...

其中:

  • CSI表示CONTROL SEQUENCE INTRODUCER,CSI是控制序列的起始标志,为Esc+[键,即\e[
  • Ps表示Parameters,Ps...表示可以多个参数。( )表示为可选参数。
  • 06/13 就是字符m,即0x6D。这是最关键的,m表示此CSI序列为SGR。
  • 参数间的分隔符是;,是在标准的5.4.2节说明的,Parameter sub-strings are separated by one bit combination 03/11.
bash 复制代码
$ echo -e '\x6d'
m
$ printf '%d\n' 0x6d
109

以下图示来自VT100的帮助文档

简单实验

SGR的参数可以从标准中获得,但最方便的还是从console_codes的man page中获得。

也可参考下表:

表1:SGR属性表

参数 含义 备注
0 Attributes off / Reset all 默认值
1 Bold or increased intensity
2 Faint, decreased intensity 较少支持
3 Italic 不一定所有终端支持
4 Underscore
5 Blink (slow)
6 Blink (rapid) 较少支持
7 Negative (reverse) image / Inverse 前景色和背景色互换
8 Conceal / Hide 文字不可见(但可选中)
9 Crossed-out / Strike-through 删除线
21 Bold off or double underline 不同终端解释不同
22 Normal intensity (neither bold nor faint)
23 Not italic
24 Underline off
25 Blink off
27 Positive (normal) image 取消反显
28 Reveal 取消隐藏
29 Not crossed-out
30--37 Set foreground color 非ECMA标准,来自DEC实现
38 Set foreground color (extended) 后接;5;n(256色)或;2;r;g;b(RGB)
39 Default foreground color
40--47 Set background color 非ECMA标准,来自DEC实现
48 Set background color (extended) 后接;5;n(256色)或;2;r;g;b(RGB)
49 Default background color
90--97 Set bright foreground color
100--107 Set bright background color

据此,我们可以做几个简单的实验:

bash 复制代码
echo;echo
printf '\e[31mRed text\e[m\n'
printf '\e[31m\e[44mRed text with blue background\e[m\n'
printf '\e[31;44mRed text with blue background\e[m\n'
printf '\e[31;44;1mBold Red text with blue background\e[m\n'
printf '\e[31;44;4mUnderline Red text with blue background\e[m\n'
printf '\e[31;7mNegative Red text with blue background\e[m\n'
printf '\e[91mBright Red text with blue background\e[m\n'
printf '\e[31;9mStrike-through Red text with blue background\e[m\n'
echo;echo

输出如下:

以上的printf也可以替换为echo -en

高阶实验

在简单实验一节的表1:SGR属性表中,我们看到参数38和48支持256色。

在console_codes的man page中描述道:

Commands 38 and 48 require further arguments:

;5;x 256 color: values 0...15 are IBGR (black, red, green, ... white), 16...231 a 6x6x6 color cube, 232...255 a grayscale ramp

;2;r;g;b 24-bit color, r/g/b components are in the range 0...255

因此可利用其做一些复杂的演示:

bash 复制代码
# 256色背景
for i in {0..256} ; do echo -en "\e[48;5;${i}m${i}\e[0m" ; done ; echo
# 256色前景
for i in {0..256} ; do echo -en "\e[38;5;${i}m${i}\e[0m" ; done ; echo

其输出如下:

以下脚本256colors.sh让输出排列更整齐:

bash 复制代码
#!/bin/bash

declare -i width
declare bg_or_fg

if [[ $# == 0 ]]; then
        echo Usage: $0 width fg|bg
        exit 0
fi


width=$1
[[ $width == 0 ]] && width=16

if [[ $2 == "fg" ]]; then
        bgfgcode=38;
elif [[ $2 == "bg" ]]; then
        bgfgcode=48;
else
        bgfgcode=38;
fi


for i in {0..255}
do
        printf "\e[${bgfgcode};5;${i}m%4d\e[0m" $i
        (( (i + 1) % $width == 0 )) && echo
done

不过这个显示只是花哨而已,最正确的方式还是要参考Bash tips: Colors and formatting (ANSI/VT100 Control sequences)中的256-colors.sh。其代码核心部分如下:

bash 复制代码
for fgbg in 38 48 ; do # Foreground / Background
    for color in {0..255} ; do # Colors
        # Display the color
        printf "\e[${fgbg};5;%sm  %3s  \e[0m" $color $color
        # Display 6 colors per lines
        if [ $((($color + 1) % 6)) == 4 ] ; then
            echo # New line
        fi
    done
    echo # New line
done

这其中有几个magic number,解读一下。

  • 38和48分别表示前景和背景
  • 6,是因为对于256色,16-231是6x6x6 color cube。因此6就是每行显示的颜色数
  • 4, 是因为对于256色,0-15是BGR,为了保证颜色16在行首,首行显示4个颜色后就必须折行

这个效果就完美了。(为方便查看,我把纵向的输出并排放了)

这也方便你理解作者以下代码的含义了:

bash 复制代码
# 渐变
for i in {16..21} {21..16} ; do echo -en "\e[38;5;${i}m#\e[0m" ; done ; echo

其他

颜色只是终端的能力之一,要拓展到终端层面,还可以看下terminfo, tput的帮助。

下面给出几个命令:

bash 复制代码
$ echo $TERM
xterm
$ BOLD=$(tput bold)
$ echo "$BOLD"|od -bc
0000000 033 133 061 155 012
        033   [   1   m  \n
0000005
# 为何红色为1,请看terminfo(5) 的 Color Handling部分
$ RED=$(tput setaf 1)
$ echo "$RED"|od -bc
0000000 033 133 063 061 155 012
        033   [   3   1   m  \n
0000006
$ tput reset
$ tput init
# 显示红色
$ printf "\e${RED}red\e[m\n"
red
## 显示红色粗体
$ printf "\e${RED}\e${BOLD}red\e[m\n"
red

参考 man page

参考文档

相关推荐
颜早早10 天前
Unreal Engine MobileFSR插件实现机制分析
图形渲染·gpu·unreal engine 5·graphic
tianyuanwo11 天前
TERM变量迷思:从Jenkins节点连接差异看终端仿真与构建系统的微妙关系
运维·ssh·jenkins·java web·term
Just_Paranoid1 个月前
【Android UI】Android 颜色的表示和获取使用指南
android·ui·theme·color·attr·colorstatelist
风浅月明7 个月前
[Harmony]颜色初始化
harmonyos·color
csdn5659738501 年前
Elasticsearch 查询时 term、match、match_phrase、match_phrase_prefix 的区别
大数据·elasticsearch·term·match_phrase
珍珠是蚌的眼泪1 年前
Elasticsearch基础_4.ES搜索功能
elasticsearch·match·filter·terms·must·term·should
大熊猫侯佩2 年前
SwiftUI 6.0(iOS 18/macOS 15)关于颜色 Color 的新玩法
color·ios 18·xcode 16·swiftui 6.0·macos 15·混合颜色·渐变动画
LabVIEW开发2 年前
LabVIEW背景颜色设为和其他程序或图像中一样
labview·labview开发·labview编程·color·图像颜色
BruceGerGer2 年前
flutter开发实战-颜色Color与16进制转换
flutter·hex·color·十六进制