如何将彩色文本输出到标准输出,同时将未着色的文本附加到文件中?

How to output colored text to stdout while also appending the text uncolored to a file?

我写了一个记录器函数,它应该将彩色文本输出到屏幕,同时使用 tee 命令将文本重定向到日志文件。

这是记录器函数:

function logger(){
    GREEN=$(tput setaf 2)
    YELLOW=$(tput setaf 3)
    BOLD=$(tput bold)
    UNDERLINE=$(tput smul)
    NOCOLOR=$(tput sgr0)

    case "" in
        y)
        echo -e -n "$(timestamp) ${YELLOW} ${NOCOLOR}\n" | tee -a $log_file
        ;;
        g)
        echo -e -n "$(timestamp) ${GREEN} ${NOCOLOR}\n" | tee -a $log_file
        ;;
        b)
        echo -e -n "$(timestamp) ${BOLD} ${NOCOLOR}\n" | tee -a $log_file
        ;;
        u)
        echo -e -n "$(timestamp) ${UNDERLINE} ${NOCOLOR}\n" | tee -a $log_file
        ;;
        n)
        echo -e -n "$(timestamp) \n" | tee -a $log_file
        ;;
        *)
        echo "Unknown color!"
        ;;
    esac
}

问题是在日志文件中写入文本时,还会应用颜色代码,这会使文本看起来很脏。

这是它的样子:

2021-08-31 12:36:41 UTC +0000 ^[[1m------------ Now working on account CompanyEUResearchAndDevelopmentJenkinsSlave in region us-east-2 ^[(B^[[m
2021-08-31 12:36:42 UTC +0000 ^[[32mThe following IPs are allocated and associated: ^[(B^[[m
2021-08-31 12:36:42 UTC +0000 13.11.131.202 18.11.34.219 3.11.227.231
2021-08-31 12:36:42 UTC +0000 ----------------------------------------------------------
2021-08-31 12:36:44 UTC +0000 ^[[33mFound unassociated elastic ips: ^[(B^[[m
2021-08-31 12:36:44 UTC +0000 18.11.91.21
2021-08-31 12:36:45 UTC +0000 ^[[33mIP 18.11.91.21 found in groups: ^[(B^[[m
2021-08-31 12:36:45 UTC +0000 sg-04b6da1d06783ffbf
2021-08-31 12:36:46 UTC +0000 ^[[32mThe rule containing ip 18.11.91.21 has been deleted from security group sg-04b6da1d06783ffbf successfully ^[(B^[[m
2021-08-31 12:36:46 UTC +0000 ^[[32mReleasing ip 18.11.91.21 with AllocationId eipalloc-07528d88e8794c6db ^[(B^[[m
2021-08-31 12:36:46 UTC +0000 ^[[32mAllocation released successfully! ^[(B^[[m
2021-08-31 12:36:47 UTC +0000 ^[[33mFound unassociated elastic ips: ^[(B^[[m
2021-08-31 12:36:47 UTC +0000 3.11.248.103
2021-08-31 12:36:48 UTC +0000 ^[[33mIP 3.11.248.103 found in groups: ^[(B^[[m
2021-08-31 12:36:48 UTC +0000 sg-04b6da1d06783ffbf
2021-08-31 12:36:49 UTC +0000 ^[[32mThe rule containing ip 3.11.248.103 has been deleted from security group sg-04b6da1d06783ffbf successfully ^[(B^[[m
2021-08-31 12:36:49 UTC +0000 ^[[32mReleasing ip 3.11.248.103 with AllocationId eipalloc-0a699e32f4ac844dc ^[(B^[[m
2021-08-31 12:36:49 UTC +0000 ^[[32mAllocation released successfully! ^[(B^[[m
2021-08-31 12:36:49 UTC +0000 End of run

函数的使用方法:

logger g "No elastic ips found in account ${role_name}"

我想知道“最正确”的方法是什么?

通过命令 ansi2txt.

tee 之前尝试管道

例如:

echo -e -n "$(timestamp) ${UNDERLINE} ${NOCOLOR}\n" | ansi2txt | tee -a $log_file

Vaphell's answer 的修改(朝向页面底部),从 appending/writing 到 $log_file 之前的 tee 流中删除了颜色代码:

改变这个:

echo -e -n "$(timestamp) ${GREEN} ${NOCOLOR}\n" | tee -a $log_file

为此:

echo -e -n "$(timestamp) ${GREEN} ${NOCOLOR}\n" | tee >(sed 's/\x1B[\[\(][0-9;]*[BJKmsu]//g' >> $log_file)

由于您可能希望经常调用 logger(),因此性能将成为一个大问题,而最大的改进将来自尽可能多地消除(不必要的)子进程调用。

tl;dr

  • 使用 2 次单独的 echo 调用,一次到标准输出,一次到 $log_file
  • 消除我假设的子进程 $(timestamp) 调用包括对 date
  • 的进一步子进程调用
  • 仅加载一次颜色变量(即 tput 子进程调用),即消除每次调用 logger()[= 时重复的 5 次子进程调用(对 tput) 109=]
  • 有关更多详细信息,请继续阅读 ...

使用 2 次单独的 echo 调用

根据 user1934428 的评论...调用 echo 两次,一次到标准输出,一次到(附加到)$log_file 是另一种选择,例如:

而不是这个:

echo -e -n "$(timestamp) ${GREEN} ${NOCOLOR}\n" | tee >(sed 's/\x1B[\[\(][0-9;]*[BJKmsu]//g' >> $log_file)

使用这个:

echo -e -n "$(timestamp) ${GREEN} ${NOCOLOR}\n"
echo -e -n "\n" >> $log_file

使用以下测试:

time for i in {1..100}
do
    mylogger g hello > /dev/null
done

备注:

  • 在我的系统上有一个 /usr/bin/logger 所以我将我的函数命名为 mylogger()
  • 我修改了我的 env 只加载一次颜色变量,而不是 OP 的当前方法每次调用函数时进行 5 次 tput 调用(否则以下测试的时间将是均匀的更糟)

结果:

real    0m8.739s       # echo ... | tee >(sed ... >> $log_file)
user    0m3.448s
sys     0m4.706s

real    0m0.105s       # echo ... ; echo ...
user    0m0.047s
sys     0m0.047s

如您所见,使用两个单独的 echo 命令将比 | tee >(sed ...) 解决方案快 LOT ... 或者任何其他需要进行(不必要的)子流程调用的解决方案

消除子进程$(timestamp)(和date)调用

我假设 $(timestamp) 调用是一个自定义 function/binary,它从当前 date 生成一个 date/time 字符串;如果 运行ning bash 4.2(或更好)考虑使用 printf 生成 date/time 字符串并存储在局部变量中,例如:

printf -v NOW '%(%F %H:%M:%S %Z %z)T' -1

备注:

  • 这不需要对 date 的子进程调用,因此 运行 会快很多
  • 有关详细信息,请参阅 this SE answer
  • 一般的想法是直接在 logger() 函数中进行 printf 调用,从而消除 $(timestamp) 子进程调用

只加载一次颜色变量(即,tput 子进程调用)

正如我在我的一条评论中提到的,考虑一次填充颜色变量,从而消除每次调用 logger() 函数时对 tput 进行 5 次子进程调用的当前过程;一个非常简单的解决方案,继续使用 OP 当前的 <color>=$(tput ...) 格式:

function load_colors() {

    if [[ -z "${NOCOLOR}" ]]
    then
        export GREEN=$(tput setaf 2)
        export YELLOW=$(tput setaf 3)
        export BOLD=$(tput bold)
        export UNDERLINE=$(tput smul)
        export NOCOLOR=$(tput sgr0)
    
    fi
}

function logger() {

    load_colors

    case "" in
    ... snip ...
    esac
}