七 echo 有时也尴尬

七 echo 有时也尴尬

【动机】

你遇到过想简单的打印孤零零的 -e 吗?

可以看到, 由于 printf 和 echo 均把 -e 当做选项, 只有最后一种方式: 采用 ascii 码方能凑效. 那么可不可以再简单一点呢?

【意图】

通过改造 echo , 使之只能识别 -e 是选项还是字符(串), 可达到目的.

【实现】

在 console.sh 中, 新建函数 echox, 如下:

:<<COMMENT
    更智能化的 echo 版本
    一. 用于解决 echo 无法单独打印 -e -n -nn -ee -EE -neEEE -nnnnee 之类字符串(以下成为类选项字符串)的问题, 
        并兼顾了输出字符串的精确还原(包括其 ascii 码). 

    二. 确保 echo 的原生命令行选项有效, 例如 -n -ne -nE 还包括了可用但反人类的 -eE -neE -nneeEE 等 

    原因:
        1.  echo 的选项为 -e -n -E, 如果想打印此三类重复字符或者其组合, 
            echo 只会将其理解成选项不显示或仅仅显示为换行符因为它们被解释成选项, 而不是字符串

        2.  printf 的选项只有 -v, 但是, 使用 printf 但不启用格式, 
            字符串只要与 - 开头均报错, 除了单个 - 字符构成的字符串以外. 原因在于 
            printf 将它们全部理解为不存在或存在的选项, 只不过对于存在的选项 -v, 
            报错内容稍有不同而已

        3.  如果简单采用 printf 的格式输出 printf "%s" "$str", 但 str 中包含转义字符
            时, 转义字符无法得到实施.

    实现原理:
        1.  从参数中剥离出真实的选项, 以确保 echo 的原生命令行选项有效
                如果只有一个参数, 哪怕是 -n -e -eee 之类, 我们认为它是要打印的内容,
                即第一个参数要成为选项, 必须至少有第二个参数才有意义.

        2.  创建先导字符串: 空格+退格. 
            加入先导空格是为了避免类选项字符串的输出误判, 加入退格是为了平衡添加的空格

        3.  欺骗 echo, 认为上面提到的类选项字符串, 它不是选项. 
            通过将先导字符串添加到打印内容头部得到合成字符串 composite. 

        4.  生成转义字符可能已经成功转义的更新的合成字符串 composite2.
            用前面得到的 echo 的原生 option, 结合 composite 得到.

        5.  评估 option 是否要求抑制末尾换行.
                由于函数返回 echo 得到的 composite2, 即使 option 不包含 -n, 
                composite2 末尾仍然没有换行符(函数返回时, 最后一个换行符自动被掐掉)
                故而在 printf 最终输出前, 还需要再次检查 option 是否不包含 n, 以决定
                在 printf 的格式字符串里, 末尾是否添加换行符.  

        6. printf "%s" ...... 格式输出. 
            为了最终 ascii 码的正确, 而不仅仅是打印的效果显示, 需要从第三个字符开始, 避开上面添加的
            前缀字符串, 达到目的. 
COMMENT
echox(){
    local option

    # 1.  从参数中剥离出真实的选项, 以确保 echo 的原生命令行选项有效
    if [[ "$1" =~ ^-[neE]+$ && "$2" ]];then
        option=$1
        shift 1
    fi

    # 2.  创建先导字符串: 空格+退格.
    local prefix=$(echo -e " \b")

    # 3.  欺骗 echo, 认为类选项字符串, 它不是选项.
    local composite=$prefix$@

    # 4.  生成转义字符可能已经成功转义的更新的合成字符串 composite2.
    local composite2=$(echo $option "$prefix$@")

    # 5.  评估 option 是否要求抑制末尾换行.
    local tail
    [[ ! "$option" =~ n ]] && tail='\n' || :

    # 6. printf "%s" ...... 格式输出
    printf "%s$tail" "${composite2:2}" 
}

【测试】

测试代码如下:

    local str

    caption "1.echo 的选项为 -e -n -E, 此三类重复字符以及其组合, \
echo 不显示或仅仅显示为换行符因为它们被解释成选项, 而不是字符串"
    local strs1=(
        "-e"
        "-n"
        "-E"
        "-eeeeee"
        "-nnnnn"
        "-EEEE"
        "-eeennnn"
        "-eeeeeEEEee"
        "-nnnnEEE"
    )
    for str in ${strs1[@]};do
        info "now echo $str alonely:"
        echo "$str"
    done

    caption "2.printf 的选项只有 -v, 但是, 使用 printf 而不启用格式, \
字符串只要与 - 开头均报错, 除了单个 - 字符构成的字符串以外. 原因在于 \
printf 将它们全部理解为不存在或存在的选项, 只不过对于存在的选项 -v, 报错内容稍有不同而已. \
注意为了顺利实验, 已保证 errexit off, 同时完事后恢复, 不论之前为 on 还是 off"
    local strs2=(
        "-"     # 这个可以正常打印
        "--"
        "----"
        "-abcde"
        "-v"
        "-vsliej"
    )
    pushee
    set +e
    for str in ${strs2[@]}; do
        info -e "\nnow printf $str without format:"
        printf "$str"
    done
    popee

    caption "3. 以上字符串, k可以考虑采用 printf 带格式输出, 必要时在格式末尾添加换行符即可:"
    for str in ${strs1[@]} ${strs2[@]}; do
        info -e "\nuse printf $str with format:"
        printf "%s" "$str"
    done

    caption -e "\n4. 但 3 的用法也不是万能的, 例如, 当字符串包含换行符, 制表符等转义字符, \
这些字符是得不到解释而转义的, 因为它们没有位于格式字符串中"
    printf "%s" "one\ntwo\tthree"

    caption -e "\n5. 综合考虑以上情况,  最佳使用方式还是 echo , 但需要一点小技巧: 先空格, 再退格"
    echo -e " \bone\ntwo\tthree"

    caption "6. 仅仅是为了转义字符倒无需 5 的技巧, 这主要是应对 1 提到的"类选项字符串":"
    for str in ${strs1[@]};do
        info "now echo $str alonely:"
        echo -e " \b$str"
    done

    caption "7. 所以, 创建函数 console.sh/echox, 封装此类问题的解决, 并保留 echo 的原有合理功能. \
同时, 兼顾了实际字符的精确还原, 即不将添加的空格和退格字符带入. 则 1 2 提到的 echo 与 printf 无法打印的问题圆满解决"
    for str in ${strs1[@]} ${strs2[@]};do
        info "now echo $str alonely:"
        echox  $str
    done    
    
    caption -e "8. 最后, 通过查验 ascii 码, 确认该方法确实可行:"
    local temp="hello\n\rworld"
    echo -ne "$temp" | od -A n -t u1
    echox -ne "$temp" | od -A n -t u1

    caption -e "9. 当 info tip notice 等方法的内部调用, 改为 echox; aop 的默认方法改为 echox 后, \
原来能用的仍然正常, 而现在终于可以在中间打印孤零零的 -e 了:"
    menu -e "hello \nworld\rXYZ"
    aop tip center "-e"

【效果】