Removendo cores da saída

140

Eu tenho algum script que produz saída com cores e preciso remover os códigos ANSI.

#!/bin/bash

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

./somescript

A saída é (no arquivo de log):

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

Eu não sabia como colocar o caractere ESC aqui, então coloquei @em seu lugar.

Eu mudei o script para:

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

Mas agora ele me fornece (no arquivo de log):

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

Como também posso remover isso ' @[60G?

Talvez haja uma maneira de desativar completamente a coloração de todo o script?

Pawel P.
fonte
Para o nó / npm, você pode usar strip-ansi: github.com/chalk/strip-ansi .
Joshua Pinter

Respostas:

165

Segundo a Wikipedia , o [m|K]no sedcomando que você está usando é especificamente projetado para lidar com m(o comando de cor) e K(a "parte de apagamento da linha de comando"). Seu script está tentando definir a posição absoluta do cursor como 60 ( ^[[60G) para obter todos os OKs em uma linha, que sua sedlinha não cobre.

(Devidamente, [m|K]provavelmente deveria ser (m|K)ou [mK], porque você não está tentando corresponder a um caractere de pipe. Mas isso não é importante no momento.)

Se você alternar a correspondência final em seu comando para [mGK]ou (m|G|K), poderá capturar essa sequência de controle extra.

./somescript | sed -r "s/\x1B\[([0-9]{1,3}(;[0-9]{1,2})?)?[mGK]//g"
Jeff Bowman
fonte
29
Usuários de BSD / OSX: Normalmente, não temos a opção -r para sed. brew install gnu-sedinstalará uma versão capaz. Corra com gsed.
Nicolai S
1
Se sim echo "$(tput setaf 1)foo$(tput sgr0) bar" | sed -r "s/\x1B\[([0-9]{1,2}(;[0-9]{1,2})?)?[mGK]//g" | cat -A, entendo: foo^O bar$então acho que alguns caracteres não foram removidos corretamente, certo? Você sabe como corrigir?
Edi9999 23/02
1
@ edi9999 Até onde sei, a diferença é que as configurações de cores além de 16 cores (como setafsuporte) exigem mais parâmetros do que apenas dois; meu regex suporta dois. Alterar a primeira ?saída *deve ajudar. A manipulação sgr0é possível, mas com base em uma pesquisa, ela provavelmente cresce fora do escopo desta resposta baseada em regex hacky.
Jeff Bowman
Ok, eu adicionei uma resposta que adiciona um sedpara o tubo para retirar a "mudança de" caráter
edi9999
7
Isso não funciona de maneira confiável, pois pode haver um terceiro valor (ala [38;5;45m). Esta resposta alternativa funciona unix.stackexchange.com/a/55547/168277
davemyron
30

Não consegui resultados decentes com nenhuma das outras respostas, mas o seguinte funcionou para mim:

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

Se eu apenas removi o caractere de controle "^ [", ele deixou o restante dos dados de cores, por exemplo, "33m". Incluir o código de cores e "m" fez o truque. Estou intrigado com s / \ x1B // g não funciona porque \ x1B [31m certamente funciona com eco.

JoeAndrieu
fonte
6
No OSX (BSD sed), use em -Evez de -rpara regex estendido. Mais pode ser encontrado aqui
Assambar
i teve que substituir {1,3}a {,3}(caso contrário ele ainda estava pulando alguns controles), graças para a sua solução!
ação 17/04
6
Como podem ser vários números separados por ponto e vírgula (para cor de fundo, negrito, itálico, etc ...). Este comando funcionou para mim:sed -r "s/[[:cntrl:]]\[([0-9]{1,3};)*[0-9]{1,3}m//g"
saeedgnu
Este (dos muitos que eu testei) trabalhou com a saída Ansible que foi executada com o buffer.
Martin
23

IMHO, a maioria dessas respostas se esforça demais para restringir o que está dentro do código de escape. Como resultado, eles acabam perdendo códigos comuns como [38;5;60m(cor ANSI 60 de primeiro plano no modo de 256 cores).

Eles também exigem a -ropção que permite extensões GNU . Estes não são necessários; eles apenas fazem o regex ler melhor.

Aqui está uma resposta mais simples que lida com as fugas de 256 cores e funciona em sistemas com não-GNU sed:

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

Isso irá capturar qualquer coisa que comece com [, tenha qualquer número de casas decimais e ponto-e-vírgula e termine com uma letra. Isso deve capturar qualquer uma das seqüências de escape ANSI comuns .

Para diversão, aqui está uma solução maior e mais geral (mas minimamente testada) para todas as seqüências de escape ANSI concebíveis :

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

(e se você tiver o problema de SI do @ edi9999, adicione | sed "s/\x0f//g"até o final; isso funciona para qualquer caractere de controle substituindo 0fpelo hexadecimal do caractere indesejado)

meustrus
fonte
Este funcionou bem para remover a cor da saída pretendida do az azi do Azure.
volvox
Fixed @elig. Acontece que ele teve vários problemas, começando com algum editor substituindo todos os meus traços por versões estranhas de unicode, mas também um monte de escapamentos impróprios - |no sed, ]dentro de uma classe de caracteres no sed e 'em uma sequência de caracteres de citação única. Agora ele está trabalhando para mim em um caso de teste muito básico.
meustrus 23/03
20

Para Mac OSX ou BSD, use

./somescript | sed $'s,\x1b\\[[0-9;]*[a-zA-Z],,g'
grebulon
fonte
1
Estranho, este funcionou bem para o debian, mas o outro acima não.
cy8g3n 5/12/19
Este funcionou parcialmente. No entanto, se eu abrir um arquivo no Excel, ainda estou vendo esse caractere especial "?" no final de cada linha.
doudy_05
@ doudy_05 Tente passar o -Esinalizador para sed para ativar o regexp estendido.
Alexander Zinchenko
14

Eu também tive o problema de que, às vezes, o personagem SI aparecia.

Aconteceu, por exemplo, com esta entrada: echo "$(tput setaf 1)foo$(tput sgr0) bar"

Aqui está uma maneira de também remover o caractere SI (shift in) (0x0f)

./somescript | sed -r "s/\x1B\[([0-9]{1,2}(;[0-9]{1,2})?)?[mGK]//g" | sed "s/\x0f//g"
edi9999
fonte
2
Não sei por que essa resposta recebe tão pouco crédito. Este é o único trabalho para mim ...
m8mble
8

Hmm, não tenho certeza se isso funcionará para você, mas 'tr' irá 'remover' (excluir) os códigos de controle - tente:

./somescript | tr -d '[:cntrl:]'
Dale_Reagan
fonte
32
De repente, também remove novas linhas
ruX
Sim, LF e CR (códigos) são códigos de controle; se você estiver interessado em mais de uma linha, talvez isso não seja uma solução. Como parece que você está executando um programa JAVA, acho que as cores são gerenciadas a partir daí; Caso contrário, você precisaria examinar a configuração do console (ou seja, configurações do terminal / esquema de cores) e / ou as opções para cada comando que suporta 'cores', ou seja, ls --color = never
Dale_Reagan
3
Gosto desta resposta por sua elegância, mesmo que faça mais do que apenas remover cores. Obrigado!
Johann Philipp Strathausen
7
na verdade, deixa códigos lá, veja ls -l + your command: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/
Para Kra
7

Eu tive um problema parecido. Todas as soluções que encontrei funcionaram bem para os códigos de cores, mas não removeram os caracteres adicionados por "$(tput sgr0)"(redefinindo atributos).

Tomando, por exemplo, a solução no comentário de davemyron, o comprimento da sequência resultante no exemplo abaixo é 9, não 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}

Para funcionar corretamente, o regex precisou ser estendido para corresponder também à sequência adicionada por sgr0(" \E(B"):

string_sed="$( sed -r "s/\x1B(\[[0-9;]*[JKmsu]|\(B)//g" <<< "${string}" )"
Jarodiv
fonte
@ Jarodiv - obrigado pela abordagem mais abrangente. Todas as respostas fornecidas neste tópico tratam APENAS das seqüências de controle ANSI / VT100 (ex: "\ e [31mOlá Mundo \ e [0m"), no entanto, não corrigem nada causado pela formatação de texto TPUT (por exemplo: tput smso / tput setaf X / tput rmso / tput sgr0). Como resultado, depois de todas as execuções 'sed', havia outra bagunça restante nos logs. Esta é uma solução pura para os meus casos!
sem rosto
5

Função muito mais simples no Bash puro para filtrar códigos ANSI comuns de um fluxo de texto:

# 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
}

Vejo:

  1. linuxjournal.com: Globbing estendido
  2. gnu.org: Expansão de parâmetros Bash
Léa Gris
fonte
1
Isso não funciona. Teste com tldr. (Embora eu uso zsh por isso pode ser também por causa disso.)
HappyFace
De fato, o Zsh não entenderá a extensão alargada de Bash extglobou provavelmente também não entenderá a substituição de cadeias por completo.
Léa Gris
Eu habilitei o estendido glob do zsh ... A substituição de string também deve ser posix?
HappyFace 28/07/19
A substituição da cadeia não é POSIX. Você pode usar qualquer um dos métodos alternativos usando os sedmencionados aqui que funcionarão com o Zsh.
Léa Gris
Esta solução tem a vantagem de fazer buffer de linha no texto. Eu tentei com o sed, mas estava bloqueando meu cachimbo.
Guillermo Prandi
3

A solução da @ jeff-bowman me ajudou a me livrar de ALGUNS códigos de cores. Adicionei outra pequena porção ao regex para remover um pouco mais:

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)
zstolar
fonte
2

Aqui está uma solução Bash pura.

Salve como strip-escape-codes.sh, torne executável e execute <command-producing-colorful-output> | ./strip-escape-codes.sh.

Observe que isso remove todos os códigos / seqüências de escape ANSI. Se você deseja descascar apenas cores, substitua [a-zA-Z]por "m".

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
Maxxim
fonte
Bem, essa solução pode ser ainda menos complicada.
Alexander Zinchenko
1

A idéia controversa seria reconfigurar as configurações do terminal para esse ambiente de processo para permitir que o processo saiba que o terminal não suporta cores.

Algo como TERM=xterm-mono ./somescriptvem à minha mente. YMMV com seu sistema operacional específico e capacidade de seu script para entender as configurações de cores dos terminais.

AB
fonte
-7

Isso funciona para mim:

./somescript | cat
spiderlama
fonte
3
Isso depende de como somescripté implementado. Pode ou não reconhecer que sua saída padrão é um tty. (As palavras infratores realmente codificam códigos de escape específicos do terminal no programa e quebram horrivelmente quando usadas em outros terminais ou em scripts).
precisa
Obrigado Toby. Usei o manage.py do django para testar, mas o que você disse faz sentido.
27617 spiderlama