Posso configurar meu shell para imprimir STDERR e STDOUT em cores diferentes?

60

Eu quero configurar meu terminal para que stderr seja impresso em uma cor diferente de stdout ; talvez vermelho. Isso tornaria mais fácil distinguir os dois.

Existe uma maneira de configurar isso em .bashrc ? Se não, isso é possível?

Observação : essa questão foi mesclada com outro que pediu stderr , stdout e o eco de entrada do usuário serão emitidos em 3 cores diferentes . As respostas podem estar respondendo a uma das perguntas.

    
por Naftuli Kay 02.05.2011 / 00:59

6 respostas

32

Esta é uma versão mais difícil de Mostra apenas stderr na tela, mas escreve stdout e stderr no arquivo .

Os aplicativos em execução no terminal usam um único canal para se comunicar com ele; as aplicações têm duas portas de saída, stdout e stderr, mas ambas estão conectadas ao mesmo canal.

Você pode conectar um deles a um canal diferente, adicionar cor a esse canal e mesclar os dois canais, mas isso causará dois problemas:

  • A saída mesclada pode não estar exatamente na mesma ordem como se não houvesse redirecionamento. Isso ocorre porque o processamento adicionado em um canal leva (um pouco) tempo, portanto, o canal colorido pode ser atrasado. Se qualquer buffering for feito, o distúrbio será pior.
  • Os terminais usam seqüências de escape de mudança de cor para determinar a cor de exibição, por exemplo ␛[31m significa "mudar para o primeiro plano vermelho". Isso significa que, se alguma saída destinada ao stdout chegar, assim como alguma saída do stderr estiver sendo exibida, a saída será descolorida. (Pior ainda, se houver um canal alternando no meio de uma seqüência de escape, você verá lixo.)

Em princípio, seria possível escrever um programa que escuta em dois momentos, de forma síncrona (ou seja, não aceita entrada em um canal enquanto processa a saída no outro canal), e imediatamente envia para o terminal com cores apropriadas. mudando instruções. Você perderia a capacidade de executar programas que interagem com o terminal. Eu não sei de nenhuma implementação desse método.

Outra abordagem possível seria fazer com que o programa exibisse as seqüências de mudança de cor adequadas, conectando todas as funções libc que chamam a chamada de sistema write em uma biblioteca carregada com LD_PRELOAD . Veja a resposta do doente para uma implementação existente, ou a resposta de Stéphane Chazelas para uma abordagem mista que aproveita strace .

Na prática, se isso for aplicável, sugiro redirecionar o stderr para stdout e piping em um colorizador baseado em padrões, como colortail ou multitail , ou colorizadores para fins especiais, como colorgcc ou colormake .

¹ pseudo-terminais. Pipes não funcionariam por causa do buffer: a fonte poderia gravar no buffer, o que quebraria a sincronicidade com o colorizador.

    
por 02.05.2011 / 01:50
36

Confira stderred . Ele usa LD_PRELOAD para enganchar em libc write() chamadas, colorindo todo stderr indo para um terminal. (Em vermelho por padrão.)

    
por 13.12.2011 / 22:45
12

É difícil colorir a entrada do usuário porque, na metade dos casos, ela é enviada pelo driver do terminal (com eco local), portanto, nenhum aplicativo em execução nesse terminal pode saber quando o usuário vai digitar o texto e alterar o saída de cor em conformidade. Apenas o driver do pseudo-terminal (no kernel) sabe (o emulador de terminal (como o xterm) envia alguns caracteres em algumas teclas e o driver do terminal pode enviar alguns caracteres para o eco, mas o xterm não pode saber se eles são do eco local ou de qual a saída do aplicativo para o lado do escravo do pseudo-terminal).

E então, há o outro modo em que o driver do terminal é avisado para não fazer eco, mas o aplicativo desta vez exibe algo. O aplicativo (como aqueles que usam readline como gdb, bash ...) pode enviar isso em seu stdout ou stderr, o que vai ser difícil diferenciar de algo que ele gera para outras coisas do que retornar a entrada do usuário.

Em seguida, para diferenciar o stdout de um aplicativo do seu stderr, existem várias abordagens.

Muitos deles envolvem o redirecionamento dos comandos stdout e stderr para pipes e os pipes lidos por um aplicativo para colori-lo. Existem dois problemas com isso:

  • Uma vez que o stdout não é mais um terminal (como um pipe), muitos aplicativos tendem a adaptar seu comportamento para iniciar o armazenamento em buffer de sua saída, o que significa que a saída será exibida em grandes blocos.
  • Mesmo que seja o mesmo processo que processa os dois canais, não há garantia de que a ordem do texto escrito pelo aplicativo em stdout e stderr será preservada, pois o processo de leitura não pode saber (se houver algo a ser lido de ambos) se deve começar a ler a partir do tubo "stdout" ou do tubo "stderr".

Outra abordagem é modificar o aplicativo para que ele colore seu stdout e stdin. Muitas vezes não é possível ou realista de fazer.

Em seguida, um truque (para aplicativos vinculados dinamicamente) pode ser seqüestrar (usando $LD_PRELOAD como na resposta de sickill ) as funções de saída chamadas pelo aplicativo para produzir algo e incluir código nelas que define a cor de primeiro plano com base em se elas devem produzir algo em stderr ou stdout. No entanto, isso significa seqüestrar todas as funções possíveis da biblioteca C e de qualquer outra biblioteca que execute write(2) syscall diretamente chamado pelo aplicativo que possa acabar escrevendo algo em stdout ou stderr (printf, puts, perror ...), e, mesmo assim, isso pode modificar seu comportamento.

Outra abordagem poderia ser usar truques PTRACE como strace ou gdb fazer para ligar-se a cada vez que a chamada de sistema write(2) é chamada e definir a cor de saída com base no fato de write(2) estar no descritor de arquivo 1 ou 2.

No entanto, isso é muito importante.

Um truque com o qual eu tenho jogado é seqüestrar o próprio strace (que faz o trabalho sujo de se ligar antes de cada chamada do sistema) usando LD_PRELOAD, para dizer a ele para mudar a cor de saída com base no fato de ter detectou um write(2) em fd 1 ou 2.

Olhando para strace código-fonte, podemos ver que todas as saídas são feitas através da função vfprintf . Tudo o que precisamos fazer é roubar essa função.

O wrapper LD_PRELOAD seria parecido com:

#define _GNU_SOURCE
#include <dlfcn.h>
#include <string.h>
#include <stdio.h>
#include <stdarg.h>
#include <unistd.h>

int vfprintf(FILE *outf, const char *fmt, va_list ap)
{
  static int (*orig_vfprintf) (FILE*, const char *, va_list) = 0;
  static int c = 0;
  va_list ap_orig;
  va_copy(ap_orig, ap);
  if (!orig_vfprintf) {
    orig_vfprintf = (int (*) (FILE*, const char *, va_list))
      dlsym (RTLD_NEXT, "vfprintf");
  }

  if (strcmp(fmt, "%ld, ") == 0) {
    int fd = va_arg(ap, long);
    switch (fd) {
    case 2:
      write(2, "\e[31m", 5);
      c = 1;
      break;
    case 1:
      write(2, "\e[32m", 5);
      c = 1;
      break;
    }
  } else if (strcmp(fmt, ") ") == 0) {
    if (c) write(2, "\e[m", 3);
    c = 0;
  }
  return orig_vfprintf(outf, fmt, ap_orig);
}

Depois, compilamos com:

cc -Wall -fpic -shared -o wrap.so wrap.c -ldl

E use-o como:

LD_PRELOAD=/path/to/wrap.so strace -qfo /dev/null -e write -s 0 env -u LD_PRELOAD some-cmd

Você notará como se você substitui some-cmd por bash , o prompt do bash e o que você digita aparecem em vermelho (stderr) enquanto zsh aparece em preto (porque zsh dups stderr em um novo fd para exibir seu prompt e eco).

Parece funcionar surpreendentemente bem mesmo para aplicativos que você não esperaria (como os que usam cores).

O modo de coloração é exibido no stderr de strace , que é considerado o terminal. Se o aplicativo redirecionar sua stdout ou stderr, nossa strace sequestrada continuará gravando as sequências de escape de coloração no terminal.

Essa solução tem suas limitações:

  • Aqueles inerentes a strace : problemas de desempenho, você não pode executar outros comandos PTRACE como strace ou gdb , ou problemas setuid / setgid
  • Sua coloração é baseada no write s no stdout / stderr de cada processo individual. Assim, por exemplo, em sh -c 'echo error >&2' , error seria verde porque echo gera em seu stdout (que é redirecionado para stderr de sh, mas todo strace vê um write(1, "error\n", 6) ). E em sh -c 'seq 1000000 | wc' , seq faz muito ou write s para seu stdout, então o wrapper acabará produzindo muitas sequências de escape (invisíveis) para o terminal.
por 01.11.2012 / 22:45
4

Aqui está uma prova de conceito que fiz um tempo atrás.

Funciona apenas no zsh.

# make standard error red
rederr()
{
    while read -r line
    do
        setcolor $errorcolor
        echo "$line"
        setcolor normal
    done
}

errorcolor=red

errfifo=${TMPDIR:-/tmp}/errfifo.$$
mkfifo $errfifo
# to silence the line telling us what job number the background job is
exec 2>/dev/null
rederr <$errfifo&
errpid=$!
disown %+
exec 2>$errfifo

Também assume que você tem uma função chamada setcolor.

Uma versão simplificada:

setcolor()
{
    case "$1" in
    red)
        tput setaf 1
        ;;
    normal)
        tput sgr0
        ;;
    esac
}
    
por 02.05.2011 / 02:19
4

Veja Hilite de Mike Schiraldi, que faz isso para um comando de cada vez. Meu próprio gush faz isso para uma sessão inteira, mas também tem muitos outros recursos / idiossincrasias que você pode não querer.

    
por 03.05.2011 / 23:55
1

Uma discussão anterior sobre serverfault

Veja também grc e uma ajuda blog sobre isso.

    
por 01.11.2012 / 19:41