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

63

Quero configurar meu terminal para que stderrseja impresso em uma cor diferente de stdout; talvez vermelho. Isso tornaria mais fácil diferenciar os dois.

Existe uma maneira de configurar isso .bashrc? Caso contrário, isso é possível?


Nota : Esta pergunta foi fundida com outra que pediu stderr, stdout e a entrada do usuário ecoar a ser saída em 3 cores diferentes . As respostas podem estar abordando qualquer uma das perguntas.

Naftuli Kay
fonte
11
Mesma pergunta no Stack Overflow: stackoverflow.com/questions/6841143/…
Stéphane Gimenez
Pergunta interessante + respostas, no entanto vermelho destaca-se demasiado IMO desde stderr não é apenas para erros
krookedking

Respostas:

32

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

Os aplicativos em execução no terminal usam um único canal para se comunicar com ele; os aplicativos têm duas portas de saída, stdout e stderr, mas ambos estão conectados 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 dos canais leva (um pouco) tempo, portanto, o canal colorido pode sofrer um atraso. Se qualquer buffer for feito, o distúrbio será pior.
  • Os terminais usam seqüências de escape de alteração de cor para determinar a cor da tela, por exemplo, ␛[31msignifica "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á diferente. (Pior ainda, se houver uma troca de canal no meio de uma sequência de escape, você verá lixo.)

Em princípio, seria possível escrever um programa que escute em dois ptys¹, de forma síncrona (ou seja, não aceitará entrada em um canal enquanto estiver processando a saída no outro canal) e imediatamente envia para o terminal com instruções de alteração de cor apropriadas. Você perderia a capacidade de executar programas que interagem com o terminal. Não conheço nenhuma implementação desse método.

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

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

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

Gilles 'SO- parar de ser mau'
fonte
11
Pode não ser difícil corrigir um programa terminal para colorir o fluxo stderr. Alguém sugeriu algo assim no brainstorm do ubuntu .
intuited
@ intuited: isso exigiria localizar todos os emuladores de terminal com os quais você deseja que isso funcione. Usando LD_PRELOADtruque para interceptar writechamadas parece ser o mais adequado, IMO (mas, novamente, pode haver diferenças em certos sabores * nix.)
alex
Pelo menos no Linux, interceptando writepor si só não iria funcionar como a maioria dos aplicativos não chamar diretamente, mas uma outra função de alguma biblioteca compartilhada (como printf) que chamaria o originalwrite
Stéphane Chazelas
@StephaneChazelas Eu estava pensando em usar o writeinvólucro syscall. Está embutido em outras funções no Glibc?
Gilles 'SO- stop be evil'
11
O projeto stderred parece ser uma implementação de conexão writevia LD_PRELOADcomo você descreve.
de Drew Noakes
36

Confira stderred. Ele usa LD_PRELOADpara ligar para libcas write()chamadas, colorindo toda a stderrsaída que vai para um terminal. (Em vermelho por padrão.)

doença
fonte
8
Bom, essa biblioteca é incrível . A verdadeira questão é: por que meu sistema operacional / terminal não vem com este pré-instalado? ;)
Naftuli Kay
5
Eu suponho que você é o autor, certo? Você deve divulgar sua afiliação nesse caso.
Dmitry Grigoryev
15

A coloração da entrada do usuário é difícil porque, na metade dos casos, ela é emitida pelo driver do terminal (com eco local); nesse caso, nenhum aplicativo em execução nesse terminal pode saber quando o usuário digitará o texto e alterará a cor de saída de acordo. . Somente o driver do pseudo-terminal (no kernel) sabe (o emulador de terminal (como o xterm) envia alguns caracteres ao pressionar algumas teclas e o driver do terminal pode enviar alguns caracteres de volta para eco, mas o xterm não pode saber se eles são do eco local ou a partir da saída do aplicativo para o lado escravo do pseudo terminal).

E há o outro modo em que o driver do terminal é instruído a não ecoar, mas o aplicativo dessa vez gera algo. O aplicativo (como aqueles que usam readline como gdb, bash ...) pode enviar isso em seu stdout ou stderr, o que dificulta a diferenciação de algo que ele gera para outras coisas além de ecoar a entrada do usuário.

Então, para diferenciar o stdout de um aplicativo do stderr, existem várias abordagens.

Muitos deles envolvem o redirecionamento dos comandos stdout e stderr para pipes e esses pipes são 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), muitas aplicações tendem a adaptar seu comportamento para iniciar o 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 em que o texto escrito pelo aplicativo em stdout e stderr seja preservada, pois o processo de leitura não pode saber (se há algo a ser lido pelos dois) se deseja iniciar a leitura do tubo "stdout" ou do tubo "stderr".

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

Então, um truque (para aplicativos vinculados dinamicamente) pode ser seqüestrar (usando $LD_PRELOADcomo 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 do primeiro plano com base no objetivo de 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 write(2)execute um syscall diretamente chamado pelo aplicativo que possa acabar escrevendo algo em stdout ou stderr (printf, puts, perror ...) e, mesmo assim, , que pode modificar seu comportamento.

Outra abordagem poderia ser usar truques PTRACE como straceou gdbfazer para se conectar sempre que a write(2)chamada do sistema for chamada e definir a cor da saída com base no write(2)descritor de arquivo 1 ou 2.

No entanto, isso é algo muito importante a se fazer.

Um truque com o qual acabei de brincar é sequestrar a stracesi próprio (que faz o trabalho sujo de ligar-se antes de cada chamada do sistema) usando LD_PRELOAD, para dizer a ele para alterar a cor de saída com base em se detectou um write(2)no fd 1 ou 2)

De olhar para straceo código-fonte, podemos ver que tudo o que produz é feito através da vfprintffunção. Tudo o que precisamos fazer é invadir essa função.

O wrapper LD_PRELOAD se pareceria 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);
}

Em seguida, 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ê substituir some-cmdpor bash, o prompt do bash e o que você digitar aparecer em vermelho (stderr) enquanto zshestiver 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 aqueles que usam cores).

O modo de coloração é emitido no stracestderr do que é assumido como o terminal. Se o aplicativo redirecionar seu stdout ou stderr, nosso rastreamento seqüestrado continuará gravando as seqüências de escape de cores no terminal.

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

  • Aqueles inerente a strace: problemas de desempenho, você não pode executar outros comandos ptrace como straceou gdbna mesma, ou questões setuid / setgid
  • É uma coloração baseada nos writepadrões stdout / stderr de cada processo individual. Assim, por exemplo, em sh -c 'echo error >&2', errorseria verde porque o echoexibe em seu stdout (o qual sh redirecionou para o stderr de sh, mas tudo que o strace vê é a write(1, "error\n", 6)). E sh -c 'seq 1000000 | wc', como seqfaz muito ou não writeao seu padrão, o wrapper acaba gerando muitas seqüências de escape (invisíveis) no terminal.
Stéphane Chazelas
fonte
Agradável. Havia sugestões de wrappers preexistentes na pergunta duplicada . Sinalizei a pergunta por mesclar para que sua resposta possa ser vista lá.
Gilles 'SO- stop being evil' em
Talvez ajustar o destaque da sintaxe do vim? strace $CMD | vim -c ':set syntax=strace' -.
Pablo A
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 pressupõe que você tenha uma função chamada setcolor.

Uma versão simplificada:

setcolor()
{
    case "$1" in
    red)
        tput setaf 1
        ;;
    normal)
        tput sgr0
        ;;
    esac
}
Mikel
fonte
Há uma maneira muito mais simples de fazer isso: exec 2> >(rederr). Ambas as versões terão os problemas mencionados na minha resposta, de reordenar linhas e arriscar uma saída mutilada (particularmente com longas filas).
Gilles 'SO- stop be evil'
Eu tentei isso e não funcionou.
Mikel
seterrteria que ser um script independente, não uma função.
Gilles 'SO- stop be evil'
4

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

Colin Macleod
fonte