【问题标题】:Removing colors from output从输出中删除颜色
【发布时间】:2013-08-02 15:59:56
【问题描述】:

我有一些产生颜色输出的脚本,我需要删除 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

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

【问题讨论】:

标签: bash unix colors console ansi-escape


【解决方案1】:

According to Wikipedia,您正在使用的sed 命令中的[m|K] 专门用于处理m(颜色命令)和K(“擦除行的一部分”命令)。您的脚本正在尝试将绝对光标位置设置为 60 (^[[60G) 以获取一行中的所有 OK,而您的 sed 行没有涵盖。

(正确地说,[m|K] 应该是 (m|K)[mK],因为您不是要匹配管道字符。但这现在并不重要。)

如果您将命令中的最终匹配切换为 [mGK](m|G|K),您应该能够捕捉到额外的控制序列。

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

【讨论】:

  • BSD/OSX 用户:我们通常没有 sed 的 -r 选项。 brew install gnu-sed 将安装一个有能力的版本。使用gsed 运行。
  • 如果我这样做 echo "$(tput setaf 1)foo$(tput sgr0) bar" | sed -r "s/\x1B\[([0-9]{1,2}(;[0-9]{1,2})?)?[mGK]//g" | cat -A ,我会得到:foo^O bar$ 所以我猜有些字符没有被正确删除,对吧?你知道如何改正吗?
  • @edi9999 据我所知,不同之处在于超过 16 种颜色的颜色设置(setaf 支持)需要的参数不仅仅是两个;我的正则表达式支持两个。将第一个 ? 更改为 * 应该会有所帮助。处理sgr0 是可能的,但根据搜索,它可能会超出这个基于正则表达式的hacky 答案的范围。
  • 这不能可靠地工作,因为可能有第三个值(ala [38;5;45m)。这个替代答案有效unix.stackexchange.com/a/55547/168277
  • 将此作为alias decolorize='sed -r "s/\\x1B\\[([0-9]{1,3}(;[0-9]{1,2})?)?[mGK]//g"' 添加到您的 bashrc 会创建一个非常好的实用程序,可以用作 command | decolorizedecolorize file.log
【解决方案2】:

恕我直言,这些答案中的大多数都试图限制转义码中的内容。因此,它们最终会丢失常见的代码,例如 [38;5;60m(256 色模式的前景 ANSI 颜色 60)。

他们还需要启用GNU extensions-r 选项。这些不是必需的;他们只是让正则表达式读起来更好。

这是一个更简单的答案,它处理 256 色转义并适用于非 GNU sed 的系统:

./somescript | sed 's/\x1B\[[0-9;]\{1,\}[A-Za-z]//g'

这将捕获以[ 开头、包含任意数量的小数和分号并以字母结尾的任何内容。这应该会捕获任何common ANSI escape sequences

为了有趣,这里有一个更大、更通用(但经过最低限度测试)的解决方案 all conceivable ANSI escape sequences

./somescript | sed 's/\x1B[@A-Z\\\]^_]\|\x1B\[[0-9:;<=>?]*[-!"#$%&'"'"'()*+,.\/]*[][\\@A-Z^_`a-z{|}~]//g'

(如果您有 @edi9999 的 SI 问题,请将 | sed "s/\x0f//g" 添加到末尾;这适用于 any control char,方法是将 0f 替换为不需要的字符的十六进制)

【讨论】:

  • 这个可以很好地从 Azure az cli 美化输出中提取颜色。
  • 固定@elig。原来它有很多问题,首先是一些编辑器用奇怪的 unicode 版本替换了我所有的破折号,还有一堆不正确的转义 - sed 中的|,sed 中的字符类中的],以及'在单引号 bash 字符串中。它现在对我来说是一个非常基本的测试用例。
  • 我认为第一个正则表达式可能有错误 - \+ 将使加号成为文字,但我认为这意味着要成为前一个范围的“至少一个”修饰符.
  • @halfer,当使用不带-r 选项的sed 时,+ 被视为文字,\+ 被视为修饰符,这与大多数现代用法相矛盾。跨度>
  • 这是我的首选答案,但对于我的用例有一个小问题,我正在处理的输出包含 ^[[m 未被捕获。通过修改解决./somescript | sed 's/\x1B\[[0-9;]*[A-Za-z]//g'
【解决方案3】:

我无法从任何其他答案中获得不错的结果,但以下内容对我有用:

somescript | sed -r "s/[[:cntrl:]]\[[0-9]{1,3}m//g"

如果我只删除控制字符“^[”,它会留下其余的颜色数据,例如“33m”。包括颜色代码和“m”就可以了。我对 s/\x1B//g 不起作用感到困惑,因为 \x1B[31m 肯定适用于 echo。

【讨论】:

  • 在 OSX (BSD sed) 上,使用 -E 而不是 -r 扩展正则表达式。更多可以找到here
  • 我不得不将{1,3} 替换为{,3}(否则它仍然会跳过一些控件),感谢您的解决方案!
  • 因为它们可能是用分号分隔的多个数字(用于背景颜色、粗体、斜体等...)。这个命令对我有用:sed -r "s/[[:cntrl:]]\[([0-9]{1,3};)*[0-9]{1,3}m//g"
  • 这个(我测试过的众多)与使用 unbuffer 运行的 Ansible 输出一起工作。
  • 对于那些想要使用 less 命令查看包含颜色代码的日志的人,这在 ubuntu 上对我有用。 cat errors.log | sed -r "s/[[:cntrl:]]\[[0-9]{1,3}m//g" | tee errors-copy.log | less errors-copy.log
【解决方案4】:

对于 Mac OSX 或 BSD 使用

./somescript | sed $'s,\x1b\\[[0-9;]*[a-zA-Z],,g'

【讨论】:

  • 奇怪,这个对 debian 工作得很好,但上面的其他没有。
  • 这个部分有效。但是,如果我在 excel 中打开一个文件,我仍然会看到这个特殊字符“?”在每一行的末尾。
  • @doudy_05 尝试为 sed 传递 -E 标志以启用扩展正则表达式。
【解决方案5】:

下面的正则表达式会遗漏一些ANSI Escape Codes 序列,以及3 位颜色。 ExampleFix 在 regex101.com 上。

改用这个:

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

我也遇到过有时会出现SI字符的问题。

例如,这个输入发生了:echo "$(tput setaf 1)foo$(tput sgr0) bar"

还有一种方法可以去除 SI 字符(移入)(0x0f)

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

【讨论】:

  • 不知道为什么这个答案得到这么少的信任。这是唯一一个为我工作的人......
  • 这个接近工作了,但它错过了三位数的大小写和颜色代码序列,例如:U+001B[38;2;128;128;128m。在regex101.com/r/Qjtopi/1 上查看未找到的颜色。可以在regex101.com/r/wYygBw/1 找到适合我的正则表达式
【解决方案6】:

我在 Debian 的 colorized-logs 软件包中发现了 ansi2txt 工具。该工具会从 STDIN 中删除 ANSI 控制代码。

使用示例:

./somescript | ansi2txt

源码http://github.com/kilobyte/colorized-logs

【讨论】:

    【解决方案7】:

    纯 Bash 中更简单的函数可从文本流中过滤掉常见的 ANSI 代码:

    # Strips common ANSI codes from a text stream
    
    shopt -s extglob # Enable Bash Extended Globbing expressions
    ansi_filter() {
      local line
      local IFS=
      while read -r line || [[ "$line" ]]; do
        echo "${line//$'\e'[\[(]*([0-9;])[@-n]/}"
      done
    }
    

    见:

    1. linuxjournal.com: Extended Globbing
    2. gnu.org: Bash Parameter Expansion

    【讨论】:

    • 这不起作用。使用tldr 进行测试。 (虽然我使用 zsh 所以也可能是因为这个。)
    • 确实,Zsh 不会理解 Bash 的扩展通配符 extglob 或者它可能也不会完全理解字符串替换。
    • 我确实启用了 zsh 的扩展...字符串替换也应该是 posix?
    • 字符串替换不是 POSIX。您可以使用此处提到的任何使用 sed 的替代方法,该方法适用于 Zsh。
    • 此解决方案的优势在于对文本进行行缓冲。我尝试使用 sed,但它正在对我的管道进行块缓冲。
    【解决方案8】:

    我遇到了类似的问题。我发现的所有解决方案都适用于颜色代码,但没有删除 "$(tput sgr0)" 添加的字符(重置属性)。

    comment by davemyron中的解法为例,下例中结果字符串的长度是9,而不是6:

    #!/usr/bin/env bash
    
    string="$(tput setaf 9)foobar$(tput sgr0)"
    string_sed="$( sed -r "s/\x1B\[[0-9;]*[JKmsu]//g" <<< "${string}" )"
    echo ${#string_sed}
    

    为了正常工作,必须扩展正则表达式以匹配sgr0 ("\E(B") 添加的序列:

    string_sed="$( sed -r "s/\x1B(\[[0-9;]*[JKmsu]|\(B)//g" <<< "${string}" )"
    

    【讨论】:

    • @Jarodiv - 感谢您提供最全面的方法。本主题提供的所有答案仅处理 ANSI/VT100 控制序列(例如:“\e[31mHello World\e[0m”),但不要修复由 TPUT 文本格式引起的任何问题(例如:tput smso/tput setaf X /tput rmso/tput sgr0)。结果,在所有“sed”执行之后,日志中还剩下一些其他混乱。这是我用例的纯粹解决方案!
    【解决方案9】:

    嗯,不确定这是否适合您,但 'tr' 将 'strip'(删除)控制代码 - 试试:

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

    【讨论】:

    • 突然间它也删除了新行
    • 是的,LF和CR(代码)是控制代码;如果您对多条线路感兴趣,那么这可能不是解决方案。由于您似乎正在运行一个 JAVA 程序,我猜想颜色是从那里管理的;否则,您需要查看控制台设置(即终端设置/配色方案)和/或支持“颜色”的每个命令的选项,即 ls --color=never
    • 我喜欢这个答案的优雅,即使它不仅仅是去除颜色。谢谢!
    • 它实际上让代码在那里,见 ls -l + 你的命令:rwxr-xr-x 1 tokra admin 22 Oct 18 14:21 [0m[01;36m/usr/local/opt/gradle[0m -&gt; [01;34m../Cellar/gradle/4.2.1[0m/
    • 控制代码不是 ANSI 代码。这根本不能回答问题。
    【解决方案10】:

    这是一个纯 Bash 解决方案。

    另存为strip-escape-codes.sh,使其可执行,然后运行&lt;command-producing-colorful-output&gt; | ./strip-escape-codes.sh

    请注意,这会去除 所有 ANSI 转义码/序列。如果您只想去除颜色,请将[a-zA-Z] 替换为"m"

    重击 >= 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
    

    重击

    #!/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
    

    【讨论】:

    • 好吧,这个解决方案可能更简单。
    【解决方案11】:

    @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)
    

    【讨论】:

      【解决方案12】:

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

      我想到了像TERM=xterm-mono ./somescript 这样的东西。 YMMV 与您的特定操作系统和脚本理解终端颜色设置的能力。

      【讨论】:

      • 我使用了你的解决方案,它奏效了。但是,我相信您可能需要将 if 设置为 xterm* 以外的其他值,至少在我将 TERM 设置为不是以 xterm 开头的任何内容之前,它对我不起作用。在我的情况下:TERM= ./my_script 就像一个魅力。
      【解决方案13】:

      还有一个专用工具来处理 ANSI 转义序列:ansifilter。使用默认的 --text 输出格式去除所有 ANSI 转义序列(注意:不仅仅是着色)。

      参考:https://stackoverflow.com/a/6534712

      【讨论】:

        【解决方案14】:

        不确定./somescript 中的内容,但如果转义序列不是硬编码的,您可以设置终端类型来避免它们

        TERM=dumb ./somescript 
        

        例如,如果你尝试

        TERM=dumb tput sgr0 | xxd
        

        你会看到它没有输出,而

        tput sgr0 | xxd
        00000000: 1b28 421b 5b6d                           .(B.[m
        
        

        可以(对于 xterm-256color)。

        【讨论】:

        • 迄今为止最简单的解决方案/答案!
        【解决方案15】:

        我遇到了这个问题/答案,试图做与 OP 类似的事情。我发现了一些其他有用的资源,并基于这些资源提出了一个日志脚本。在这里发帖以防它可以帮助其他人。

        挖掘链接有助于理解一些重定向,我不会尝试解释,因为我自己才刚刚开始理解它。

        Usage 会将彩色输出呈现到控制台,同时从进入日志文件的文本中去除颜色代码。它还将在日志文件中包含任何不起作用的命令的 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}"
        
        
        

        以下是其他有用的链接:

        【讨论】:

          【解决方案16】:

          我使用 perl,因为我必须经常对许多文件执行此操作。这将遍历所有文件名*.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
          

          【讨论】:

            【解决方案17】:

            我的贡献:

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

            【讨论】:

              【解决方案18】:

              这对我有用:

              ./somescript | cat
              

              【讨论】:

              • 这取决于somescript 的实现方式。它可能会也可能不会认识到它的标准输出是一个 tty。 (这些词实际上将特定于终端的转义码硬编码到程序中,并且在其他终端或脚本中使用时会严重中断)。
              • 谢谢托比。我用django的manage.py来测试,不过你说的有道理。
              猜你喜欢
              • 2021-01-07
              • 2017-09-24
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2018-01-07
              • 1970-01-01
              • 1970-01-01
              • 2020-01-07
              相关资源
              最近更新 更多