从输出中去除颜色


140

我有一些脚本可以生成带有颜色的输出,并且需要删除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

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


对于node / npm,您可以使用strip-ansigithub.com/chalk/strip-ansi
约书亚·品特

Answers:


165

根据Wikipedia的介绍,您正在使用[m|K]sed命令专门用于处理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"

29
BSD / OSX用户:我们通常没有-r选项来进行sed。brew install gnu-sed将安装一个有能力的版本。用运行gsed
Nicolai S

1
如果这样做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

1
据我所知,@ edi9999的区别在于,超过16种颜色(作为setaf支持)的颜色设置需要的参数比仅仅两个要多;我的正则表达式支持两个。改变先?*应该会有所帮助。处理sgr0是可能的,但是基于搜索,它可能超出了该基于正则表达式的答案的范围。
Jeff Bowman

好的,我已经添加了一个答案,该答案将添加sed到管道中以
去除

7
这不能可靠地工作,因为可能存在第三个值(ala [38;5;45m)。这个替代答案有效unix.stackexchange.com/a/55547/168277
davemyron

30

我无法从其他任何答案中得到令人满意的结果,但是以下内容对我有用:

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

如果我只删除了控制字符“ ^ [”,它将剩下其余的颜色数据,例如“ 33m”。包括颜色代码和“ m”就可以了。我对s / \ x1B // g感到困惑,因为\ x1B [31m当然可以在echo中工作。


6
在OSX(BSD sed)上,使用-E而不是-r扩展正则表达式。在此处
阿萨姆巴(Asambar),

我必须替换{1,3}{,3}(否则它仍在跳过某些控件),感谢您的解决方案!
无效

6
由于它们可能是多个用分号分隔的数字(背景颜色,粗体,斜体等)。该命令为我工作:sed -r "s/[[:cntrl:]]\[([0-9]{1,3};)*[0-9]{1,3}m//g"
saeedgnu

(我测试的许多测试中的其中一个)与使用unbuffer运行的Ansible输出一起使用。
马丁

23

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

他们还需要-r启用GNU扩展的选项。这些不是必需的。他们只是使正则表达式的阅读效果更好。

这是一个处理256色转义符并在非GNU的系统上工作的简单答案sed

./somescript | sed 's/\x1B\[[0-9;]\+[A-Za-z]//g'

这将捕获以开头的[,具有任意数量的小数和分号,以字母结尾的所有内容。这应该捕获任何常见的ANSI转义序列

对于趣味性,这是适用于所有可能的ANSI转义序列的更大,更通用(但经过最低测试的)解决方案:

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

(如果您有@ edi9999的SI问题,请添加| sed "s/\x0f//g"到末尾;这可以通过替换为不需要的字符的十六进制来适用于任何控制0f字符)


此代码很好地将颜色从Azure az cli预设的输出中提取出来。
volvox

固定@elig。事实证明,这有很多问题,首先是一些编辑器用奇怪的unicode版本替换了我所有的破折号,但也出现了一些不正确的转义- |在sed中,在sed ]的字符类中以及'在单引号的bash字符串中。现在它对我来说是一个非常基本的测试用例。
meustrus

20

对于Mac OSX或BSD使用

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

1
奇怪的是,这对debian来说效果很好,但以上其他方面却没有。
cy8g3n

这部分工作。但是,如果我在excel中打开文件,我仍然会看到这个特殊字符“?”。在每一行的末尾。
doudy_05 '19

@ doudy_05尝试传递-Esed标志以启用扩展的正则表达式。
Alexander Zinchenko

14

我也有有时出现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"

2
不知道为什么这个答案得不到这么多的荣誉。这是唯一为我工作的人……
m8mble

8

嗯,不确定这是否适合您,但是“ tr”会“剥离”(删除)控制代码 -尝试:

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

32
突然,它也删除了新的行
ruX

是的,LF和CR(代码)是控制代码;如果您对多个行感兴趣,那么这可能不是解决方案。由于您似乎正在运行JAVA程序,因此我猜可以从那里进行颜色管理。否则,您将需要查看控制台设置(即终端设置/配色方案)和/或每个支持“颜色”的命令的选项,即ls --color = never
Dale_Reagan

3
我喜欢这个答案,因为它很优雅,即使它不仅仅是去除颜色。谢谢!
约翰·菲利普·斯特拉特豪森,

7
它实际上让代码在那里,请参阅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/
致Kra

7

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

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”执行之后,日志中还剩下一些其他混乱。这是我的用例的纯解决方案!
不露面的

5

纯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:扩展globbing
  2. gnu.org:Bash参数扩展

1
这行不通。用测试tldr。(尽管我使用zsh,所以可能也是因为这个原因。)
HappyFace

确实,Zsh不会理解Bash的扩展范围,extglob或者可能也不会完全理解字符串替换。
LEA格里斯

我确实启用了zsh的extendedglob ...字符串替换也应该是posix吗?
HappyFace

字符串替换不是POSIX。您可以使用sed此处提及的任何其他方法来与Zsh一起使用。
LEA格里斯

该解决方案的优点是可以对文本进行行缓冲。我尝试使用sed,但是它对我的管道进行了块缓冲。
吉列尔莫·普兰迪

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)

2

这是一个纯Bash解决方案。

另存为strip-escape-codes.sh,使其成为可执行文件,然后运行<command-producing-colorful-output> | ./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

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

好吧,这种解决方案甚至可以变得更简单。
Alexander Zinchenko

1

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

TERM=xterm-mono ./somescript我想到的是某种东西。具有特定操作系统的YMMV和脚本了解终端颜色设置的能力。


-7

这对我有用:

./somescript | cat

3
这取决于如何somescript实施。它可能会或可能不会认识到其标准输出是tty。(“冒犯者”一词实际上是将特定于终端的转义代码硬编码到程序中,并且在其他终端或脚本中使用时会严重破坏)。
Toby Speight

谢谢托比。我使用django的manage.py进行测试,但是您所说的是有道理的。
Spiderlama '17
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.