¿Puedo configurar mi shell para imprimir STDERR y STDOUT en diferentes colores?


63

Quiero configurar mi terminal para que stderrse imprima en un color diferente que stdout; Quizás rojo. Esto facilitaría distinguir los dos.

¿Hay alguna manera de configurar esto .bashrc? Si no, ¿es esto posible?


Nota : Esta pregunta se fusionó con otra que solicitó stderr, stdout y el eco de entrada del usuario se emitirá en 3 colores diferentes . Las respuestas pueden estar abordando cualquiera de las preguntas.


1
Misma pregunta sobre Stack Overflow: stackoverflow.com/questions/6841143/…
Stéphane Gimenez

Preguntas + respuestas interesantes, sin embargo, el rojo se destaca demasiado OMI ya que stderr no es solo por errores
krookedking 03 de

Respuestas:


32

Esta es una versión más difícil de Mostrar solo stderr en pantalla pero escribe tanto stdout como stderr en el archivo .

Las aplicaciones que se ejecutan en el terminal usan un solo canal para comunicarse con él; las aplicaciones tienen dos puertos de salida, stdout y stderr, pero ambos están conectados al mismo canal.

Puede conectar uno de ellos a un canal diferente, agregar color a ese canal y fusionar los dos canales, pero esto causará dos problemas:

  • La salida combinada puede no estar exactamente en el mismo orden que si no hubiera habido redirección. Esto se debe a que el procesamiento agregado en uno de los canales lleva (un poco) tiempo, por lo que el canal coloreado puede retrasarse. Si se realiza alguna amortiguación, el trastorno será peor.
  • Los terminales utilizan secuencias de escape que cambian de color para determinar el color de la pantalla, por ejemplo, ␛[31msignifica "cambiar a primer plano rojo". Esto significa que si alguna salida destinada a stdout llega justo cuando se muestra alguna salida para stderr, la salida tendrá un color incorrecto. (Aún peor, si hay un interruptor de canal en medio de una secuencia de escape, verá basura).

En principio, sería posible escribir un programa que escuche en dos ptys¹, sincrónicamente (es decir, no acepte la entrada en un canal mientras procesa la salida en el otro canal), e inmediatamente saldrá al terminal con las instrucciones apropiadas de cambio de color. Perdería la capacidad de ejecutar programas que interactúan con el terminal. No sé de ninguna implementación de este método.

Otro enfoque posible sería hacer que el programa emita las secuencias de cambio de color adecuadas, enganchando todas las funciones de libc que llaman a la llamada del writesistema en una biblioteca cargada con LD_PRELOAD. Vea la respuesta de sickill para una implementación existente, o la respuesta de Stéphane Chazelas para un enfoque mixto que aprovecha strace.

En la práctica, si corresponde, sugiero redirigir stderr a stdout y canalizarlo a un colorizador basado en patrones como colortail o multitail , o colorantes especiales como colorgcc o colormake .

¹ pseudo terminales. Las tuberías no funcionarían debido al almacenamiento en búfer: la fuente podría escribir en el búfer, lo que rompería la sincronía con el colorizador.


1
Puede que no sea difícil parchear un programa de terminal para colorear el flujo stderr. Alguien ha sugerido algo así en ubuntu brainstorm .
intuido

@intuited: eso requeriría ubicar cada emulador de terminal con el que desea que esto funcione. El uso de LD_PRELOADtruco para interceptar writellamadas parece ser el más apropiado, en mi opinión (pero, de nuevo, puede haber diferencias en ciertos sabores * nix.)
alex

Al menos en Linux, interceptar writesolo no funcionaría ya que la mayoría de las aplicaciones no llaman directamente, sino otra función de alguna biblioteca compartida (como printf) que llamaría al originalwrite
Stéphane Chazelas, el

@StephaneChazelas Estaba pensando en enganchar el writeenvoltorio de syscall. ¿Está incluido en otras funciones en Glibc?
Gilles 'SO- deja de ser malvado'

1
El proyecto stderred parece ser una implementación de enganche a writetravés de lo LD_PRELOADque usted describe.
Drew Noakes

36

Echa un vistazo stderred. Se utiliza LD_PRELOADpara conectar libclas write()llamadas a, coloreando toda la stderrsalida que va a un terminal. (En rojo por defecto).


8
Bien, esa biblioteca es asombrosa . La verdadera pregunta es: ¿por qué mi sistema operativo / terminal no viene con esto preinstalado? ;)
Naftuli Kay

55
Supongo que eres el autor, ¿es así? Debe revelar su afiliación en ese caso.
Dmitry Grigoryev

15

Colorear la entrada del usuario es difícil porque en la mitad de los casos, es emitida por el controlador del terminal (con eco local), por lo que ninguna aplicación que se ejecute en ese terminal puede saber cuándo el usuario va a escribir texto y cambiar el color de salida en consecuencia . Solo el controlador de pseudo-terminal (en el núcleo) sabe (el emulador de terminal (como xterm) le envía algunos caracteres al presionar alguna tecla y el controlador de terminal puede enviar algunos caracteres para eco, pero xterm no puede saber si son del eco local o de lo que sale la aplicación al lado esclavo del pseudo terminal).

Y luego, está el otro modo en el que se le dice al controlador del terminal que no repita, pero esta vez la aplicación genera algo. La aplicación (como las que usan readline como gdb, bash ...) puede enviar eso en su stdout o stderr, lo que será difícil de diferenciar de algo que genera para otras cosas que no sean hacer eco de la entrada del usuario.

Luego, para diferenciar el stdout de una aplicación de su stderr, hay varios enfoques.

Muchos de ellos implican redirigir los comandos stdout y stderr a tuberías y esas tuberías leídas por una aplicación para colorearlo. Hay dos problemas con eso:

  • Una vez que stdout ya no es un terminal (como una tubería), muchas aplicaciones tienden a adaptar su comportamiento para comenzar a almacenar en búfer su salida, lo que significa que la salida se mostrará en grandes fragmentos.
  • Incluso si es el mismo proceso que procesa las dos canalizaciones, no hay garantía de que se mantenga el orden del texto escrito por la aplicación en stdout y stderr, ya que el proceso de lectura no puede saber (si hay algo que leer de ambos) si comenzar a leer desde la tubería "stdout" o la tubería "stderr".

Otro enfoque es modificar la aplicación para que coloree su stdout y stdin. A menudo no es posible ni realista hacerlo.

Entonces, un truco (para aplicaciones vinculadas dinámicamente) puede ser secuestrar (usando $LD_PRELOADcomo en la respuesta de sickill ) las funciones de salida llamadas por la aplicación para generar algo e incluir código en ellas que establece el color de primer plano en función de si están destinadas a generar algo en stderr o stdout. Sin embargo, eso significa secuestrar todas las funciones posibles de la biblioteca C y de cualquier otra biblioteca que write(2)realice una llamada al sistema directamente llamada por la aplicación que podría terminar escribiendo algo en stdout o stderr (printf, Put, Perror ...), e incluso entonces , eso puede modificar su comportamiento.

Otro enfoque podría ser utilizar trucos PTRACE straceo gdbengancharnos cada vez write(2)que se llama a la llamada del sistema y establecer el color de salida en función de si write(2)está en el descriptor de archivo 1 o 2.

Sin embargo, eso es bastante importante.

Un truco con el que acabo de jugar es secuestrarse a stracesí mismo (que hace el trabajo sucio de engancharse antes de cada llamada al sistema) usando LD_PRELOAD, para decirle que cambie el color de salida en función de si ha detectado un write(2)fd 1 o 2)

Al mirar el stracecódigo fuente, podemos ver que todo lo que sale se realiza a través de la vfprintffunción. Todo lo que necesitamos hacer es secuestrar esa función.

El contenedor LD_PRELOAD se vería así:

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

Luego, lo compilamos con:

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

Y úsalo como:

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

Notarás cómo si reemplazas some-cmdcon bash, el indicador bash y lo que zshescribes aparece en rojo (stderr) mientras que aparece en negro (porque zsh dups stderr en un nuevo fd para mostrar su indicador y eco).

Parece funcionar sorprendentemente bien incluso para aplicaciones que no esperarías (como las que usan colores).

El modo de coloración se emite en stracestderr, que se supone que es el terminal. Si la aplicación redirige su stdout o stderr, nuestra secuencia secuestrada seguirá escribiendo las secuencias de escape para colorear en el terminal.

Esa solución tiene sus limitaciones:

  • Aquellos inherentes a strace: problemas de rendimiento, no puede ejecutar otros comandos PTRACE como straceo gdben él, o problemas setuid / setgid
  • Es una coloración basada en el writes en stdout / stderr de cada proceso individual. Entonces, por ejemplo, en sh -c 'echo error >&2', errorsería verde porque lo echogenera en su stdout (que sh redirige a sh's stderr, pero todo lo que ve strace es a write(1, "error\n", 6)). Y en sh -c 'seq 1000000 | wc', seqhace mucho o writes a la salida estándar, por lo que el contenedor va a terminar outputing un montón de (invisible) secuencias de escape a la terminal.

Agradable. Hubo sugerencias de envoltorios preexistentes en la pregunta duplicada . Marqué la pregunta para fusionarla, de modo que su respuesta pueda verse allí.
Gilles 'SO- deja de ser malvado'

¿Quizás ajustar el resaltado de sintaxis vim? strace $CMD | vim -c ':set syntax=strace' -.
Pablo A

4

Aquí hay una prueba de concepto que hice hace un tiempo.

Solo funciona en 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

También supone que tiene una función llamada setcolor.

Una versión simplificada:

setcolor()
{
    case "$1" in
    red)
        tput setaf 1
        ;;
    normal)
        tput sgr0
        ;;
    esac
}

Hay una manera mucho más sencilla de hacer esto: exec 2> >(rederr). Ambas versiones tendrán los problemas que menciono en mi respuesta, de reordenar líneas y arriesgar la salida destrozada (particularmente con líneas largas).
Gilles 'SO- deja de ser malvado'

Lo intenté y no funcionó.
Mikel

seterrtendría que ser un script independiente, no una función.
Gilles 'SO- deja de ser malvado'

4

Ver de Mike Schiraldi Hilite que hace esto por un comando a la vez. Mi propio gush hace esto durante toda una sesión, pero también tiene muchas otras características / idiosincrasias que quizás no desee.


Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.