Posso configurare la mia shell per stampare STDERR e STDOUT in diversi colori?


63

Voglio impostare il mio terminale in modo che stderrsia stampato in un colore diverso da stdout; forse rosso. Ciò renderebbe più semplice distinguere i due.

C'è un modo per configurarlo in .bashrc? In caso contrario, è anche possibile?


Nota : La domanda è stata fusa con un'altra che ha chiesto stderr, stdout e l'input dell'utente eco da emettere in 3 diversi colori . Le risposte possono rispondere a entrambe le domande.


1
Stessa domanda su Stack Overflow: stackoverflow.com/questions/6841143/…
Stéphane Gimenez,

Interessante domanda + risposte, tuttavia il rosso spicca troppo IMO poiché stderr non è solo per errori
krookingking

Risposte:


32

Questa è una versione più difficile di Mostra solo stderr sullo schermo ma scrivi sia stdout che stderr su file .

Le applicazioni in esecuzione nel terminale utilizzano un singolo canale per comunicare con esso; le applicazioni hanno due porte di output, stdout e stderr, ma sono entrambe collegate allo stesso canale.

Puoi collegarne uno a un altro canale, aggiungere colore a quel canale e unire i due canali, ma ciò causerà due problemi:

  • L'output unito potrebbe non essere esattamente nello stesso ordine come se non ci fosse stato il reindirizzamento. Questo perché l'elaborazione aggiunta su uno dei canali richiede (un po ') tempo, quindi il canale colorato potrebbe essere ritardato. Se viene eseguito un buffering, il disturbo peggiorerà.
  • I terminali utilizzano sequenze di escape che cambiano colore per determinare il colore del display, ad esempio ␛[31msignifica "passare al primo piano rosso". Ciò significa che se un output destinato a stdout arriva proprio mentre viene visualizzato un output per stderr, l'output verrà erroneamente colorato. (Ancora peggio, se c'è un interruttore di canale nel mezzo di una sequenza di escape, vedrai la spazzatura.)

In linea di principio, sarebbe possibile scrivere un programma in ascolto su due ptys¹, in modo sincrono (ovvero non accetterà l'input su un canale mentre sta elaborando l'output sull'altro canale), e immediatamente output al terminale con le istruzioni di cambio colore appropriate. Perderesti la capacità di eseguire programmi che interagiscono con il terminale. Non conosco alcuna implementazione di questo metodo.

Un altro possibile approccio sarebbe quello di far sì che il programma emetta le sequenze di cambio colore appropriate, agganciando tutte le funzioni libc che chiamano la writechiamata di sistema in una libreria caricata LD_PRELOAD. Vedi la risposta di sickill per un'implementazione esistente o la risposta di Stéphane Chazelas per un approccio misto che sfrutta strace.

In pratica, se applicabile, suggerisco di reindirizzare stderr a stdout e piping in un colorante basato su motivi come colortail o multitail o coloranti per scopi speciali come colorgcc o colormake .

¹ pseudo-terminali. Le pipe non funzionavano a causa del buffering: l'origine poteva scrivere nel buffer, il che avrebbe interrotto la sincronicità con il colorizer.


1
Potrebbe non essere difficile patchare un programma terminale per colorare il flusso stderr. Qualcuno ha suggerito qualcosa del genere durante il brainstorming di Ubuntu .
intuito

@intuited: ciò richiederebbe il percorso di ogni emulatore di terminale con cui vuoi che funzioni. Usare il LD_PRELOADtrucco per intercettare le writechiamate sembra essere il più appropriato, IMO (ma, di nuovo, potrebbero esserci differenze su alcuni tipi di * nix.)
alex

Almeno su Linux, l'intercettazione writeda sola non funzionerebbe poiché la maggior parte delle applicazioni non chiama direttamente, ma un'altra funzione di una libreria condivisa (come printf) che chiamerebbe l'originalewrite
Stéphane Chazelas,

@StephaneChazelas Stavo pensando di agganciarmi al writewrapper syscall. È integrato in altre funzioni di Glibc?
Gilles 'SO- smetti di essere malvagio'

1
Il progetto stderred sembra essere un'implementazione dell'aggancio writetramite LD_PRELOADcome descrivi.
Ha disegnato Noakes il

36

Partenza stderred. Usa LD_PRELOADper agganciare libcle write()chiamate, colorando tutto l' stderroutput che va a un terminale. (In rosso per impostazione predefinita.)


8
Bene, quella biblioteca è fantastica . La vera domanda è: perché il mio sistema operativo / terminale non viene fornito con questo preinstallato? ;)
Naftuli Kay il

5
Presumo che tu sia l'autore, giusto? Dovresti rivelare la tua affiliazione in quel caso.
Dmitry Grigoryev il

15

La colorazione dell'input dell'utente è difficile perché nella metà dei casi viene emessa dal driver del terminale (con eco locale), in tal caso, nessuna applicazione in esecuzione in quel terminale può sapere quando l'utente sta per digitare il testo e modificare il colore di output di conseguenza . Solo il driver pseudo-terminale (nel kernel) sa (l'emulatore di terminale (come xterm) invia alcuni caratteri su un tasto premuto e il driver del terminale può inviare alcuni caratteri per l'eco, ma xterm non può sapere se quelli provengono dal eco locale o da ciò che l'applicazione invia al lato slave dello pseudo terminale).

E poi, c'è l'altra modalità in cui viene detto al driver del terminale di non eco, ma l'applicazione questa volta emette qualcosa. L'applicazione (come quelli che usano readline come gdb, bash ...) può inviarlo sul suo stdout o stderr che sarà difficile distinguere da qualcosa che emette per altre cose oltre a riecheggiare l'input dell'utente.

Quindi, per differenziare lo stdout di un'applicazione dal suo stderr, ci sono diversi approcci.

Molti di essi comportano il reindirizzamento dei comandi stdout e stderr su pipe e quei pipe letti da un'applicazione per colorarlo. Ci sono due problemi con quello:

  • Una volta che stdout non è più un terminale (come una pipe invece), molte applicazioni tendono ad adattare il loro comportamento per iniziare a bufferizzare il loro output, il che significa che l'output verrà visualizzato in grossi blocchi.
  • Anche se è lo stesso processo che elabora le due pipe, non vi è alcuna garanzia che l'ordine del testo scritto dall'applicazione su stdout e stderr venga preservato, poiché il processo di lettura non può sapere (se c'è qualcosa da leggere da entrambi) se iniziare a leggere dalla pipe "stdout" o dalla pipe "stderr".

Un altro approccio è quello di modificare l'applicazione in modo che colora il suo stdout e lo stdin. Spesso non è possibile o realistico da fare.

Quindi un trucco (per applicazioni collegate dinamicamente) può essere quello di dirottare (usando $LD_PRELOADcome nella risposta di sickill ) le funzioni di emissione chiamate dall'applicazione per generare qualcosa e includere codice in esse che imposta il colore di primo piano in base al fatto che siano destinate a produrre qualcosa su stderr o stdout. Tuttavia, ciò significa dirottare ogni possibile funzione dalla libreria C e da qualsiasi altra libreria che esegue una write(2)chiamata di chiamata diretta dall'applicazione che potrebbe potenzialmente finire per scrivere qualcosa su stdout o stderr (printf, put, perror ...), e anche allora , che potrebbe modificarne il comportamento.

Un altro approccio potrebbe essere quello di utilizzare i trucchi PTRACE come straceo gdbfare per agganciarsi ogni volta write(2)che viene chiamata la chiamata di sistema e impostare il colore di output in base al fatto che write(2)sia sul descrittore di file 1 o 2.

Tuttavia, questa è una cosa abbastanza grande da fare.

Un trucco con cui ho appena giocato è dirottare stracese stesso (che fa il lavoro sporco di agganciarsi da solo prima di ogni chiamata di sistema) usando LD_PRELOAD, per dirgli di cambiare il colore di output in base al fatto che abbia rilevato un write(2)su fd 1 o 2.

Osservando straceil codice sorgente, possiamo vedere che tutto ciò che produce viene fatto tramite la vfprintffunzione. Tutto quello che dobbiamo fare è dirottare quella funzione.

Il wrapper LD_PRELOAD sarebbe simile a:

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

Quindi, lo compiliamo con:

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

E usalo come:

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

Noterai come se lo sostituisci some-cmdcon bash, il prompt di bash e ciò che scrivi appare in rosso (stderr) mentre con zshesso appare in nero (perché zsh duplica stderr su un nuovo fd per visualizzare il suo prompt ed eco).

Sembra funzionare sorprendentemente bene anche per le applicazioni che non ti aspetteresti (come quelle che usano i colori).

La modalità di colorazione viene emessa sullo stracestderr che si presume sia il terminale. Se l'applicazione reindirizza il suo stdout o stderr, la nostra sequenza dirottata continuerà a scrivere le sequenze di escape della colorazione sul terminale.

Questa soluzione ha i suoi limiti:

  • Quelli inerenti a strace: problemi di prestazioni, non è possibile eseguire altri comandi PTRACE come straceo gdbin esso, o problemi setuid / setgid
  • Si tratta della colorazione basata sulla writes su stdout / stderr di ogni singolo processo. Ad esempio, in sh -c 'echo error >&2', errorsarebbe verde perché lo echoemette sul suo stdout (che sh reindirizza allo shd stderr, ma tutto lo strace vede è a write(1, "error\n", 6)). E dentro sh -c 'seq 1000000 | wc', seqfa molto writeper il suo stdout, quindi il wrapper finirà con l'output di molte sequenze di escape (invisibili) sul terminale.

Bello. Sulla domanda duplicata c'erano suggerimenti di involucri preesistenti . Ho contrassegnato la domanda per la fusione in modo che la tua risposta possa essere vista lì.
Gilles 'SO- smetti di essere malvagio'

Forse ottimizzando l'evidenziazione della sintassi di VIM? strace $CMD | vim -c ':set syntax=strace' -.
Pablo A

4

Ecco una prova del concetto che ho fatto tempo fa.

Funziona solo in 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

Presuppone anche che tu abbia una funzione chiamata setcolor.

Una versione semplificata:

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

C'è un modo molto più semplice per fare questo: exec 2> >(rederr). Entrambe le versioni avranno i problemi che menziono nella mia risposta, il riordino delle linee e il rischio di output alterato (in particolare con le linee lunghe).
Gilles 'SO- smetti di essere malvagio'

L'ho provato e non ha funzionato.
Mikel,

seterrdovrebbe essere uno script autonomo, non una funzione.
Gilles 'SO- smetti di essere malvagio'

4

Vedi Hilite di Mike Schiraldi che lo fa per un comando alla volta. My gush lo fa per un'intera sessione, ma ha anche molte altre caratteristiche / idiosincrasie che potresti non desiderare.


Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.