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_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 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 strace
ou gdb
fazer 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 strace
si 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 strace
o código-fonte, podemos ver que tudo o que produz é feito através da vfprintf
funçã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-cmd
por bash
, o prompt do bash e o que você digitar aparecer em vermelho (stderr) enquanto zsh
estiver 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 strace
stderr 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 strace
ou gdb
na mesma, ou questões setuid / setgid
- É uma coloração baseada nos
write
padrões stdout / stderr de cada processo individual. Assim, por exemplo, em sh -c 'echo error >&2'
, error
seria verde porque o echo
exibe 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 seq
faz muito ou não write
ao seu padrão, o wrapper acaba gerando muitas seqüências de escape (invisíveis) no terminal.