Menghapus warna dari keluaran

149

Saya memiliki beberapa skrip yang menghasilkan keluaran dengan warna dan saya perlu menghapus kode ANSI.

#!/bin/bash

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

./somescript

Outputnya adalah (dalam file log):

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

Saya tidak tahu bagaimana meletakkan karakter ESC di sini, jadi saya menempatkannya @.

Saya mengubah skrip menjadi:

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

Tapi sekarang memberi saya (dalam file log):

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

Bagaimana saya juga bisa menghapus ini ' @[60G?

Mungkin ada cara untuk sepenuhnya menonaktifkan pewarnaan untuk seluruh skrip?

Pawel P.
sumber
Untuk node / npm, Anda dapat menggunakan strip-ansi: github.com/chalk/strip-ansi .
Joshua Pinter

Jawaban:

176

Menurut Wikipedia , yang [m|K]dalam sedperintah yang Anda gunakan secara khusus dirancang untuk menangani m(perintah warna) dan K(yang "menghapus bagian dari baris" perintah). Skrip Anda mencoba menyetel posisi kursor absolut ke 60 ( ^[[60G) untuk mendapatkan semua OK dalam satu baris, yang sedtidak dicakup baris Anda .

(Sebenarnya, [m|K]mungkin harus (m|K)atau [mK], karena Anda tidak mencoba mencocokkan karakter pipa. Tapi itu tidak penting sekarang.)

Jika Anda mengalihkan pertandingan terakhir itu dalam perintah Anda ke [mGK]atau (m|G|K), Anda harus bisa menangkap urutan kontrol ekstra itu.

./somescript | sed -r "s/\x1B\[([0-9]{1,3}(;[0-9]{1,2})?)?[mGK]//g"
Jeff Bowman
sumber
34
Pengguna BSD / OSX: Kami biasanya tidak memiliki opsi -r untuk sed. brew install gnu-sedakan menginstal versi yang mampu. Jalankan dengan gsed.
Nicolai S
1
Jika ya echo "$(tput setaf 1)foo$(tput sgr0) bar" | sed -r "s/\x1B\[([0-9]{1,2}(;[0-9]{1,2})?)?[mGK]//g" | cat -A, saya mendapatkan: foo^O bar$Jadi saya kira beberapa karakter tidak dihapus dengan benar, bukan? Apakah Anda tahu cara mengoreksi?
edi9999
1
@ edi9999 Sejauh yang saya tahu, perbedaannya adalah bahwa pengaturan warna di luar 16 warna (sebagai setafpendukung) memerlukan lebih banyak parameter daripada hanya dua; regex saya mendukung dua. Mengganti yang pertama ?untuk *seharusnya membantu. Penanganan sgr0dimungkinkan tetapi berdasarkan pencarian, kemungkinan itu tumbuh di luar cakupan jawaban berbasis regex yang hacky ini.
Jeff Bowman
Oke, saya telah menambahkan jawaban yang menambahkan a sedke pipa untuk menghapus karakter "shift in"
edi9999
7
Ini tidak bekerja dengan andal karena mungkin ada nilai ketiga (ala [38;5;45m). Jawaban alternatif ini berfungsi unix.stackexchange.com/a/55547/168277
davemyron
32

Saya tidak bisa mendapatkan hasil yang layak dari jawaban lain, tetapi yang berikut berhasil untuk saya:

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

Jika saya hanya menghapus kontrol char "^ [", itu meninggalkan sisa data warna, misalnya "33m". Memasukkan kode warna dan "m" berhasil. Saya bingung dengan s / \ x1B // g tidak berfungsi karena \ x1B [31m pasti berfungsi dengan echo.

JoeAndrieu
sumber
6
Di OSX (BSD sed), gunakan -Esebagai pengganti -rregex yang diperpanjang. Lebih lanjut bisa ditemukan di sini
Assambar
saya harus mengganti {1,3}ke {,3}(jika tidak, masih melewatkan beberapa kontrol), terima kasih atas solusi Anda!
bertindak
6
Karena mereka mungkin beberapa angka yang dipisahkan dengan titik koma (untuk warna latar belakang, tebal, miring, dll ...). Perintah ini berhasil untuk saya:sed -r "s/[[:cntrl:]]\[([0-9]{1,3};)*[0-9]{1,3}m//g"
saeedgnu
Yang ini (dari banyak yang saya uji) bekerja dengan keluaran Ansible yang telah dijalankan dengan unbuffer.
Martin
30

IMHO, sebagian besar jawaban ini berusaha terlalu keras untuk membatasi apa yang ada di dalam kode pelarian. Akibatnya, mereka kehilangan kode umum seperti [38;5;60m(ANSI warna latar depan 60 dari mode 256 warna).

Mereka juga membutuhkan -ropsi yang mengaktifkan ekstensi GNU . Ini tidak diperlukan; mereka hanya membuat ekspresi reguler menjadi lebih baik.

Berikut adalah jawaban sederhana yang menangani pelarian 256 warna dan berfungsi pada sistem dengan non-GNU sed:

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

Ini akan menangkap apa pun yang dimulai dengan [, memiliki sejumlah desimal dan titik koma, dan diakhiri dengan huruf. Ini harus menangkap salah satu urutan escape ANSI yang umum .

Untuk funsies, berikut adalah solusi yang lebih besar dan lebih umum (tapi minimal teruji) untuk semua escape sequence ANSI :

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

(dan jika Anda memiliki masalah SI @ edi9999, tambahkan | sed "s/\x0f//g"ke bagian akhir; ini berfungsi untuk semua karakter kontrol dengan mengganti 0fdengan hex dari karakter yang tidak diinginkan)

meustrus
sumber
Yang ini bekerja dengan baik untuk mengeluarkan warna dari keluaran Azure az cli prettified.
volvox
Memperbaiki @elig. Ternyata itu memiliki sejumlah masalah, dimulai dengan beberapa editor mengganti semua tanda hubung saya dengan versi unicode yang aneh, tetapi juga sekelompok pelarian yang tidak tepat - |di sed, ]di dalam kelas karakter di sed, dan 'dalam string bash yang dikutip tunggal. Sekarang ini berfungsi untuk saya untuk kasus uji yang sangat dasar.
meustrus
Saya pikir mungkin ada kesalahan dengan ekspresi reguler pertama - \+akan membuat tanda plus menjadi literal, tetapi saya pikir itu berarti menjadi pengubah "setidaknya satu" dari rentang sebelumnya.
halfer
@halfer, saat digunakan sedtanpa -ropsi, +diperlakukan sebagai literal dan \+diperlakukan sebagai pengubah, yang bertentangan dengan sebagian besar penggunaan modern.
meustrus
@meustrus: ah benar, terima kasih. Karena minat, apakah ini perilaku di tingkat shell atau di tingkat Sed? Kedengarannya seperti Anda mendeskripsikan sesuatu dalam Sed, yang saya setuju adalah sintaks yang tidak terduga!
halfer
24

Untuk penggunaan Mac OSX atau BSD

./somescript | sed $'s,\x1b\\[[0-9;]*[a-zA-Z],,g'
grebulon
sumber
1
Aneh, yang satu ini bekerja dengan baik untuk debian tetapi yang lain di atas tidak.
cy8g3n
Yang ini berfungsi sebagian. Namun, jika saya membuka file di excel, saya masih melihat karakter khusus ini "?" di akhir setiap baris.
doudy_05
@ doudy_05 Cobalah untuk melewatkan -Ebendera sed untuk mengaktifkan regexp diperpanjang.
Alexander Zinchenko
14

Saya juga sempat bermasalah, kadang karakter SI muncul.

Itu terjadi misalnya dengan masukan ini: echo "$(tput setaf 1)foo$(tput sgr0) bar"

Berikut cara untuk juga menghapus karakter SI (shift in) (0x0f)

./somescript | sed -r "s/\x1B\[([0-9]{1,2}(;[0-9]{1,2})?)?[mGK]//g" | sed "s/\x0f//g"
edi9999
sumber
2
Tidak yakin mengapa jawaban ini menerima begitu sedikit pujian. Ini adalah satu-satunya yang bekerja untuk saya ...
m8mble
8

Hmm, tidak yakin apakah ini akan berhasil untuk Anda, tetapi 'tr' akan 'strip' (hapus) kode kontrol - coba:

./somescript | tr -d '[:cntrl:]'
Dale_Reagan
sumber
37
Tiba-tiba itu juga menghapus baris baru
ruX
Ya, LF dan CR (kode) adalah kode kontrol; jika Anda tertarik pada lebih dari satu baris maka ini mungkin bukan solusi. Karena tampaknya Anda menjalankan program JAVA, saya rasa warna-warna tersebut dikelola dari sana; Jika tidak, Anda perlu melihat pengaturan konsol Anda (yaitu pengaturan terminal / skema warna) dan / atau pada opsi untuk setiap perintah yang mendukung 'warna', yaitu ls --color = never
Dale_Reagan
4
Saya suka jawaban ini karena keanggunannya, meskipun tidak hanya menghilangkan warna. Terima kasih!
Johann Philipp Strathausen
7
itu benar-benar membiarkan kode di sana, lihat ls -l + perintah Anda: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/
Untuk Kra
7

Saya punya masalah serupa. Semua solusi yang saya temukan berfungsi dengan baik untuk kode warna tetapi tidak menghapus karakter yang ditambahkan oleh "$(tput sgr0)"(mengatur ulang atribut).

Mengambil, misalnya, solusi dalam komentar oleh davemyron , panjang string yang dihasilkan pada contoh di bawah ini adalah 9, bukan 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}

Agar berfungsi dengan baik, regex harus diperluas agar juga cocok dengan urutan yang ditambahkan oleh sgr0(" \E(B"):

string_sed="$( sed -r "s/\x1B(\[[0-9;]*[JKmsu]|\(B)//g" <<< "${string}" )"
Jarodiv
sumber
@Jarodiv - terima kasih atas pendekatan yang paling komprehensif. Semua jawaban yang diberikan pada topik ini HANYA berurusan dengan ANSI / VT100 Control sequence (contoh: "\ e [31mHello World \ e [0m"), namun tidak memperbaiki apa pun yang disebabkan oleh format teks TPUT (mis .: tput smso / tput setaf X / tput rmso / tput sgr0). Akibatnya setelah semua eksekusi 'sed' ada beberapa kekacauan lain yang tersisa di log. Ini adalah solusi murni untuk kasus penggunaan saya!
tak berwajah
6

Fungsi yang jauh lebih sederhana dalam Bash murni untuk memfilter kode ANSI umum dari aliran teks:

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

Lihat:

  1. linuxjournal.com: Globbing yang Diperpanjang
  2. gnu.org: Perluasan Parameter Bash
Léa Gris
sumber
1
Ini tidak berhasil. Uji dengan tldr. (Meskipun saya menggunakan zsh jadi mungkin juga karena itu.)
HappyFace
Memang, Zsh extglobtidak akan memahami perluasan globing Bash atau mungkin juga tidak akan memahami penggantian string sama sekali.
Léa Gris
Saya memang mengaktifkan extendedglob dari zsh ... Penggantian string harus posix juga?
HappyFace
Penggantian string bukan POSIX. Anda dapat menggunakan salah satu metode alternatif menggunakan yang seddisebutkan di sini yang akan bekerja dengan Zsh.
Léa Gris
Solusi ini memiliki keuntungan dari baris-buffering teks. Saya mencoba dengan sed tetapi itu memblokir buffering pipa saya.
Guillermo Prandi
3

Solusi @ jeff-bowman membantu saya menyingkirkan BEBERAPA kode warna. Saya menambahkan sebagian kecil lagi ke regex untuk menghapus lagi:

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
sumber
2

Inilah solusi Bash murni.

Simpan sebagai strip-escape-codes.sh, buat dapat dieksekusi, lalu jalankan <command-producing-colorful-output> | ./strip-escape-codes.sh.

Perhatikan bahwa ini menghapus semua kode / urutan escape ANSI. Jika Anda hanya ingin menghapus warna, ganti [a-zA-Z]dengan "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
sumber
Nah, solusi ini bisa jadi tidak terlalu rumit.
Alexander Zinchenko
2

Ide kontroversialnya adalah mengkonfigurasi ulang pengaturan terminal untuk lingkungan proses ini agar proses mengetahui bahwa terminal tidak mendukung warna.

Sesuatu seperti TERM=xterm-mono ./somescriptmuncul di benak saya. YMMV dengan OS spesifik Anda dan kemampuan skrip Anda untuk memahami pengaturan warna terminal.

AB
sumber
0

Saya menemukan pertanyaan / jawaban ini mencoba melakukan sesuatu yang mirip dengan OP. Saya menemukan beberapa sumber berguna lainnya dan menghasilkan skrip log berdasarkan itu. Posting di sini seandainya dapat membantu orang lain.

Menggali tautan membantu memahami beberapa pengalihan yang tidak akan saya coba dan jelaskan karena saya sendiri baru mulai memahaminya.

Penggunaan akan membuat keluaran berwarna ke konsol, sambil menghapus kode warna dari teks menuju ke file log. Ini juga akan menyertakan stderr di logfile untuk setiap perintah yang tidak berfungsi.

Edit: menambahkan lebih banyak penggunaan di bagian bawah untuk menunjukkan cara masuk dengan berbagai cara

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


Berikut tautan lain yang membantu:

Carlos Soriano
sumber
0

Tidak yakin apa yang ada di ./somescriptdalamnya tetapi jika urutan pelolosan tidak di-hardcode, Anda dapat menyetel jenis terminal untuk menghindarinya

TERM=dumb ./somescript 

Misalnya, jika Anda mencoba

TERM=dumb tput sgr0 | xxd

Anda akan melihatnya tidak menghasilkan keluaran sementara

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

tidak (untuk xterm-256color).

Diego Torres Milano
sumber
-6

Ini bekerja untuk saya:

./somescript | cat
spiderlama
sumber
4
Itu tergantung bagaimana somescriptimplementasinya. Mungkin atau mungkin tidak mengenali bahwa keluaran standarnya adalah tty. (Kata-kata pelanggar sebenarnya adalah kode pelarian khusus terminal kode keras ke dalam program, dan rusak secara mengerikan ketika digunakan pada terminal lain atau dalam skrip).
Toby Speight
Terima kasih Toby. Saya menggunakan manage.py django untuk menguji, tapi apa yang Anda katakan masuk akal.
spiderlama