一 颜色文本

一 颜色文本

【动机】

一般, 我们使用 printf 或 echo 输出颜色文本,大致应该这样写:

        printf "\033[0;37m%s\033[0m\n" "使用 printf 打印的文本"
        echo -e  "\e[31;44;5m使用 echo, 结合 -e 选项打印的文本 \e[0m"
        echo   "\e[31;44;5m仅仅使用 echo, 无 -e 选项则难如所愿 \e[0m"

很明显, 如果每次打印颜色文本, 都要如此录入是很不方便的.

【意图】

将 echo 或 printf 语句中与颜色相关的部分封装成函数, 使用时调用相应函数即可.

【预备知识】

关于 linux shell 颜色书写的格式, 有几点需要说明:

  1. \033 是 e 的 ascii 码, 故 \033 与 \e 两者可以互换, 看个人喜好;
  2. 背景色定义: 0 终端默认颜色, 40 黑, 41 红, 42 绿, 43 黄, 44 蓝, 45 紫, 46 青绿, 47 白(灰)
  3. 前景色定义: 0 终端默认颜色, 30 黑, 31 红, 32 绿, 33 黄, 34 蓝, 35 紫, 36 青绿, 37 白(灰)
  4. 字体特效: 1高亮, 4下划线, 5闪烁, 7背景取反. 其他值未知.
  5. \e到m之间的内容, 即以分号分隔的数字字符串(为叙述方便, 暂时称之为颜色字符串)为紧凑格式, 即只要有一个多余空格, 所有颜色属性失效;
  6. \e[ 后面的参数指定前景色,背景色,字体效果. 由于三者取值范围不同, 故它们的顺序可互换. 但如果明示某个分量为0, 则 0 必须放在最前端, 否则, 其前面的颜色属性一律失效. 例如以下前三行输出是一致的, 而最后两行的某些颜色属性标识是无效的:
    echo -e "\e[31;44;5m顺序为 31 44 5 \e[0m"
    echo -e "\e[44;31;5m顺序为 44 31 5 \e[0m"
    echo -e "\e[5;44;31m顺序为 5 44 31 \e[0m"

    echo -e "\e[5;0;31m字体闪烁特效标识 5 无效, 在于背景色 0 位于其后面 \e[0m"
    echo -e "\e[5;44;0m字体闪烁特效标识 5, 以及背景色 44(蓝色) 无效, 在于前景色 0 位于最后 \e[0m"

【初步实现】

第一种实现, 过于简单, 仅以此作为后面的铺垫.

新建文件 console.sh

创建函数 _single_color

_single_color(){
    local clr=$1
    shift 
    echo -e "\033[${clr}m$@\033[0m" 
}

显然, 调用方式为 _color_output <颜色字符串> <文本>

当然直接调用 _color_output 还是很麻烦, 故而针对几个颜色配置, 专门写了几个函数, 以 caption 函数为例:

:<<COMMENT
    打印标题文本,
    $1: text
    用法: caption <text>
COMMENT
caption(){
    _single_color "33;1" "$@"
}

【测试】

新建文件 test.sh

    # 根据实际文件位置修改路径
    # source ./console.sh
    way1(){
        caption "single text is no problem"
        caption -e "\tThe escape character failed"
        caption -n "Failed to cancel end-of-line wrap"
        echo ".........................."
    }
    clear
    way1

测试结果:

显然, 简单文本没毛病, 但是需要转义, 或者抑制末尾换行就会出现多余打印或者失效了, 因为在 _single_color 内部, echo 把选项参数(-e 和 -n)均当作纯文本显示 .

所以, 修改还是得从 _single_color 内部来.

仍然在 console.sh 中, 新建 _color_output 函数:

:<<COMMENT
    按指定颜色输出文本, 一般不做外部调用
    $1: 颜色字符串, 最多3个. 中间用分号隔开, 例如 "41;36;4"
    $2: 文本字符串
    说明: 背景色, 前景色, 效果的顺序可选, 但如果包含0, 应尽量远离 m 放置 
    因为 0 以前的数值均被忽略
    echo 的其余命令行选项仍然有效, 例如 -n -e -E, 但需要写在一起: -ne -nE
    注意: 反人类的写法, 例如 -neE, 或者 -neeee 等, 将被视为文本字符串的一部分
COMMENT
_color_output(){

    # 逐步 echo, 方可准确定位命令行选项以及文本字符串
    echo -ne "\e[${1}m" 
    local opt
    shift 1

    if [[ "$1" =~ ^-[neE]{1,2}$ ]];then
        # 确保 echo 的原生命令行选项有效, 例如 -n 
        opt=$1
        shift 1
        echo "$opt" "$@"
    else
        # 也要避免误将文本字符串中的 - 当作命令行选项处理
        echo "$@"
    fi

    echo -ne "\e[0m"
}

大体上, 要点是将格式设置字符串, 文本, 恢复字符串逐个 echo, 达到目的.

然后, caption 函数也应做相应更改

caption(){
    #_single_color "33;1" "$@"
    _color_output "33;1" "$@"
}

【再次测试】

    # 根据实际文件位置修改路径
    source ./console.sh
    way2(){
        caption "single text is no problem"
        caption -e "\tThe escape character was successfully escaped "
        caption -n "The end-of-line break was successfully suppressed"
        echo ".........................."

    }
    clear
    way2

结果如下:

可以看到, 转义字符成功转义, 同时 echo 的原生选项被正确识别, 不在被当作文本的一部分; 末尾换行符也通过 -n 选项成功抑制.

【扩展】

仅仅 caption 一个函数显然不够, 此处再给出几个颜色文本输出函数, 后续文章可能会用到, 读者可以按照此规范, 根据需要写更多

menu(){
    _color_output "36;1" "$@"   # 绿
}

notice(){
    _color_output "36;45;1" "$@"
}

tip(){
    _color_output "34;1" "$@"
}

success(){
    _color_output "33;42;1" "$@"   
}

info(){
    _color_output "30;1" "$@"  # 青绿
}

warn(){
    _color_output "33;43;1" "$@"  # 黄
}

error(){
    _color_output "33;41;1" "$@" # 红 
}

critical(){
    _color_output "36;41;5"  "$@" # 灰白底, 闪烁红
}

测试略.

最后, 如果想将输出文本做一些简单的左右对齐或 居中处理, 该怎么办? 这就是另一篇: “文本对齐打印” 的事了.