从输出中删除颜色

239

我有一些脚本会输出带着颜色的内容,我需要去除其中的ANSI码。

#!/bin/bash

exec > >(tee log)   # redirect the output to a file but keep it on stdout
exec 2>&1

./somescript

输出结果(在日志文件中):

java (pid  12321) is running...@[60G[@[0;32m  OK  @[0;39m]

我不知道如何在这里放置ESC字符,所以我用@代替它。

我将脚本改为:

#!/bin/bash

exec > >(tee log)   # redirect the output to a file but keep it on stdout
exec 2>&1

./somescript | sed -r "s/\x1B\[([0-9]{1,2}(;[0-9]{1,2})?)?[m|K]//g"

但是现在它给了我(在日志文件中):

java (pid  12321) is running...@[60G[  OK  ]

我该如何删除这个'@[60G'符号?

也许有一种方法可以完全禁用整个脚本的着色?


2
对于node/npm,您可以使用strip-ansi:https://github.com/chalk/strip-ansi。 - Joshua Pinter
这个问题之前已经有人问过并得到了答案:https://dev59.com/rmw15IYBdhLWcg3wkMjz#6534712 - mike
22个回答

4
这是一个纯Bash解决方案。将其保存为strip-escape-codes.sh,并使其可执行。运行<产生有色输出的命令> | ./strip-escape-codes.sh即可。
请注意,这会剥离所有的ANSI转义代码/序列。如果您只想剥离颜色,请用"m" 替换 [a-zA-Z]
Bash >= 4.0:
#!/usr/bin/env bash

# Strip ANSI escape codes/sequences [$1: input string, $2: target variable]
function strip_escape_codes() {
    local _input="$1" _i _char _escape=0
    local -n _output="$2"; _output=""
    for (( _i=0; _i < ${#_input}; _i++ )); do
        _char="${_input:_i:1}"
        if (( ${_escape} == 1 )); then
            if [[ "${_char}" == [a-zA-Z] ]]; then
                _escape=0
            fi
            continue
        fi
        if [[ "${_char}" == $'\e' ]]; then
            _escape=1
            continue
        fi
        _output+="${_char}"
    done
}

while read -r line; do
    strip_escape_codes "${line}" line_stripped
    echo "${line_stripped}"
done

Bash < 4.0:

#!/usr/bin/env bash

# Strip ANSI escape codes/sequences [$1: input string, $2: target variable]
function strip_escape_codes() {
    local input="${1//\"/\\\"}" output="" i char escape=0
    for (( i=0; i < ${#input}; ++i )); do         # process all characters of input string
        char="${input:i:1}"                       # get current character from input string
        if (( ${escape} == 1 )); then             # if we're currently within an escape sequence, check if
            if [[ "${char}" == [a-zA-Z] ]]; then  # end is reached, i.e. if current character is a letter
                escape=0                          # end reached, we're no longer within an escape sequence
            fi
            continue                              # skip current character, i.e. do not add to ouput
        fi
        if [[ "${char}" == $'\e' ]]; then         # if current character is '\e', we've reached the start
            escape=1                              # of an escape sequence -> set flag
            continue                              # skip current character, i.e. do not add to ouput
        fi
        output+="${char}"                         # add current character to output
    done
    eval "$2=\"${output}\""                       # assign output to target variable
}

while read -r line; do
    strip_escape_codes "${line}" line_stripped
    echo "${line_stripped}"
done

1
这个解决方案可能会更简单。 - Alexander Zinchenko

3

@jeff-bowman的解决方案帮助我去掉了一些颜色代码。我添加了一个小部分到正则表达式中以去除更多的颜色代码:

sed -r "s/\x1B\[([0-9]{1,2}(;[0-9]{1,2})?)?[mGK]//g" # Original. Removed Red ([31;40m[1m[error][0m)
sed -r "s/\x1B\[([0-9];)?([0-9]{1,2}(;[0-9]{1,2})?)?[mGK]//g" # With an addition, removed yellow and green ([1;33;40m[1m[warning][0m and [1;32;40m[1m[ok][0m)
                ^^^^^^^^^
                remove Yellow and Green (and maybe more colors)

3

这个有争议的想法是重新配置终端设置,以便让进程环境知道终端不支持颜色。

类似于 TERM=xterm-mono ./somescript 的东西跃然纸上。取决于您使用的操作系统和脚本理解终端颜色设置的能力是否足够。


1
我用了你的解决方案,它有效了。但是,我认为你可能需要将其设置为除 xterm* 之外的其他值,因为在我设置 TERM 为任何不以 xterm 开头的值之前,它对我无效。在我的情况下:TERM= ./my_script 很顺利地解决了问题。 - David Rissato Cruz

3

嗯,不确定这是否适用于您,但是'tr'命令可以删除控制字符,请尝试:

./somescript | tr -d '[:cntrl:]'

46
突然间也删除了换行符。 - ruX
1
是的,LF和CR(代码)是控制代码; 如果您想要多行文本,则可能不是这个问题的解决方案。 由于看起来您正在运行一个JAVA程序,我猜测颜色是从那里管理的; 否则,您需要查看控制台设置(即终端设置/颜色方案)和/或支持“colors”的每个命令的选项,例如ls --color = never。 - Dale_Reagan
4
我喜欢这个答案的优雅性,即使它所做的不仅仅是去除颜色。谢谢! - Johann Philipp Strathausen
10
实际上它让代码留在那里,查看 ls -l 和您的命令: rwxr-xr-x 1 tokra admin 22 Oct 18 14:21 [0m[01;36m/usr/local/opt/gradle[0m -> [01;34m../Cellar/gradle/4.2.1[0m/ - To Kra
4
控制码不是 ANSI 码。这并没有回答问题。 - itaych
1
嗯...你说得对 - 我的回答并没有直接回答关于去除颜色的问题。另外,似乎我们读的ANSI代码文档不同 - 我读过的包括“控制码”作为标准的一部分(有很多...):) - Dale_Reagan

2
我曾经遇到过颜色输出的问题,而其他解决方案无法正确处理,因此我编写了这个Perl单行程序。它会查找转义码\e后跟着开括号\[,然后是一个或多个以分号分隔的颜色代码\d+,最后以m结尾。
perl -ple 's/\e\[\d+(;\d+)*m//g'

它似乎非常适合彩色编译器输出。


0

我的贡献:

./somescript | sed -r "s/\\x1B[\\x5d\[]([0-9]{1,3}(;[0-9]{1,3})?(;[0-9]{1,3})?)?[mGK]?//g"

0
我花了一点时间寻找一个不需要记住sed命令的解决方案,并且没有在任何地方看到这个建议:
./somescript | tr -dc '[[:print:][:space:]]'
这个命令会删除任何不可打印或空白(包括垂直)的字符。

0

我在尝试做与OP类似的事情时遇到了这个问题/答案。我找到了一些其他有用的资源,并基于这些资源编写了一个日志脚本。在这里发布,以帮助他人。

深入研究链接可以帮助理解一些重定向,但我不会尝试解释,因为我自己刚开始理解它。

使用将在控制台中呈现着色输出,同时从发送到日志文件的文本中剥离颜色代码。对于任何不起作用的命令,它还将在日志文件中包括stderr。

编辑:在底部添加更多用法,以展示如何以不同方式记录日志

#!/bin/bash
set -e
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"

. $DIR/dev.conf
. $DIR/colors.cfg

filename=$(basename ${BASH_SOURCE[0]})
# remove extension
# filename=`echo $filename | grep -oP '.*?(?=\.)'`
filename=`echo $filename | awk -F\. '{print $1}'`
log=$DIR/logs/$filename-$target

if [ -f $log ]; then
  cp $log "$log.bak"
fi

exec 3>&1 4>&2
trap 'exec 2>&4 1>&3' 0 1 2 3
exec 1>$log 2>&1


# log message
log(){
    local m="$@"
    echo -e "*** ${m} ***" >&3
    echo "=================================================================================" >&3
  local r="$@"
    echo "================================================================================="
    echo -e "*** $r ***" | sed -r "s/\x1B\[([0-9]{1,2}(;[0-9]{1,2})?)?[mGK]//g"
    echo "================================================================================="
}

echo "=================================================================================" >&3
log "${Cyan}The ${Yellow}${COMPOSE_PROJECT_NAME} ${filename} ${Cyan}script has been executed${NC}"
log $(ls) #log $(<command>)

log "${Green}Apply tag to image $source with version $version${NC}"
# log $(exec docker tag $source $target 3>&2) #prints error only to console
# log $(docker tag $source $target 2>&1) #prints error to both but doesn't exit on fail
log $(docker tag $source $target 2>&1) && exit $? #prints error to both AND exits on fail
# docker tag $source $target 2>&1 | tee $log # prints gibberish to log
echo $? # prints 0 because log function was successful
log "${Purple}Push $target to acr${NC}"


以下是其他有用的链接:


0

其他答案没有完全去除所有的转义代码(例如\x1b[?25l)。

这个小的sed命令应该可以解决问题:

./somescript | sed -r 's/[\x1B\x9B][][()#;?]*(([a-zA-Z0-9;]*\x07)|([0-9;]*[0-9A-PRZcf-ntqry=><~]))//g'

正则表达式是 https://github.com/acarl005/stripansi/blob/master/stripansi.go#L7 的修改版


0

我使用Perl,因为我需要经常在许多文件上执行此操作。这将遍历所有以filename*.txt命名的文件,并删除任何格式。这适用于我的用例,对其他人也可能有用,所以我想在这里发布。请将您的文件名替换为filename*.txt,或者您可以在下面设置FILENAME变量时将文件名用空格分隔。

$ FILENAME=$(ls filename*.txt) ; for file in $(echo $FILENAME); do echo $file; cat $file | perl -pe 's/\e([^\[\]]|\[.*?[a-zA-Z]|\].*?\a)//g' | col -b > $file-new; mv $file-new $file; done

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接