Puis-je configurer mon shell pour imprimer STDERR et STDOUT dans des couleurs différentes?

63

Je souhaite configurer mon terminal de manière à ce qu'il stderrsoit imprimé dans une couleur différente de celle de stdout; peut-être rouge. Cela faciliterait la distinction entre les deux.

Y at-il un moyen de configurer cela dans .bashrc? Si non, est-ce même possible?


Remarque : Cette question a été fusionnée avec une autre qui l’a demandé stderr, stdout et l’écho d’entrée de l’utilisateur doit être imprimé dans 3 couleurs différentes . Les réponses peuvent être adresser l'une ou l'autre question.

Naftuli Kay
la source
1
Même question sur Stack Overflow: stackoverflow.com/questions/6841143/…
Stéphane Gimenez
Question + réponses intéressantes, même si le rouge ressort trop souvent à l’OMI car stderr n’est pas seulement une erreur
krookedking

Réponses:

32

Ceci est une version plus difficile de Afficher uniquement stderr à l'écran, mais écrivez stdout et stderr dans un fichier .

Les applications exécutées dans le terminal utilisent un seul canal pour communiquer avec ce dernier. les applications ont deux ports de sortie, stdout et stderr, mais elles sont toutes deux connectées au même canal.

Vous pouvez connecter l'un d'entre eux à un autre canal, ajouter de la couleur à ce canal et fusionner les deux canaux, mais cela entraînera deux problèmes:

  • La sortie fusionnée peut ne pas être exactement dans le même ordre que s'il n'y avait pas eu de redirection. En effet, le traitement ajouté sur l'un des canaux prend (un peu) de temps, le canal coloré peut donc être retardé. Si un tampon est fait, le désordre sera pire.
  • Les terminaux utilisent des séquences d'échappement à changement de couleur pour déterminer la couleur d'affichage, ce qui ␛[31msignifie par exemple «passer au premier plan rouge». Cela signifie que si une sortie destinée à stdout arrive juste au moment où une sortie pour stderr est affichée, la sortie sera mal colorée. (Pire encore, s'il y a un commutateur de canal au milieu d'une séquence d'échappement, vous verrez des déchets.)

En principe, il serait possible d'écrire un programme qui écoute sur deux ptys¹ de manière synchrone (c'est-à-dire qu'il n'acceptera pas l'entrée sur un canal alors qu'il traite la sortie sur l'autre canal) et envoie immédiatement au terminal les instructions de changement de couleur appropriées. Vous perdriez la possibilité d'exécuter des programmes qui interagissent avec le terminal. Je ne connais aucune implémentation de cette méthode.

Une autre approche possible serait de forcer le programme à afficher les séquences de changement de couleur appropriées, en interceptant toutes les fonctions de la writebibliothèque appelant l' appel système dans une bibliothèque chargée avec LD_PRELOAD. Voir la réponse de sickill pour une implémentation existante, ou la réponse de Stéphane Chazelas pour une approche mixte qui exploite strace.

En pratique, si cela s’applique, je suggère de rediriger stderr vers stdout et de rediriger vers un coloriseur basé sur des motifs, tel que colortail ou multitail , ou des coloriseurs spéciaux, tels que colorgcc ou colormake .

¹ pseudo-terminaux. Les pipes ne fonctionneraient pas à cause de la mise en mémoire tampon: la source pourrait écrire dans la mémoire tampon, ce qui romprait la synchronicité avec le coloriseur.

Gilles, arrête de faire le mal
la source
1
Il n’est peut-être pas difficile de patcher un programme terminal pour coloriser le flux stderr. Quelqu'un a suggéré quelque chose comme ceci lors du brainstorming d'ubuntu .
Intuition
@intuited: cela nécessiterait de déplacer chaque émulateur de terminal avec lequel vous voulez que cela fonctionne. Utiliser une LD_PRELOADastuce pour intercepter des writeappels semble être le plus approprié, IMO (mais là encore, il pourrait y avoir des différences sur certains goûts * nix.)
alex
Du moins sous Linux, l’interception writeseule ne fonctionnerait pas car la plupart des applications n’appelaient pas directement, mais une autre fonction d’une bibliothèque partagée (comme printf) appelant l’originalwrite
Stéphane Chazelas
@StephaneChazelas Je pensais accrocher au writewrapper Syscall. Est-il intégré dans d'autres fonctions de Glibc?
Gilles, arrête d'être méchant '
1
Le projet stderred semble être une implémentation de l’accrochage writevia ce LD_PRELOADque vous décrivez.
Drew Noakes
36

Consultez stderred. Il utilise LD_PRELOADpour accrocher à libcl » write()appel, colorisation tous stderrsortie vers un terminal. (En rouge par défaut.)

malade malade
la source
8
Nice, cette bibliothèque est géniale . La vraie question est: pourquoi mon système d’exploitation / terminal n’a-t-il pas cette installation préinstallée? ;)
Naftuli Kay
5
Je suppose que vous êtes l'auteur, est-ce exact? Vous devez indiquer votre affiliation dans ce cas.
Dmitry Grigoryev
15

La coloration de la saisie utilisateur est difficile car, dans la moitié des cas, elle est générée par le pilote de terminal (avec écho local). Dans ce cas, aucune application exécutée sur ce terminal ne peut savoir quand l’utilisateur va taper du texte et modifier la couleur de sortie en conséquence. . Seul le pilote du pseudo-terminal (dans le noyau) le sait (l’émulateur de terminal (comme xterm) lui envoie des caractères lorsqu’on appuie sur une touche et le pilote du terminal peut renvoyer des caractères pour l’écho, mais xterm ne peut pas savoir si ceux-ci proviennent du écho local ou de ce que l’application a envoyé au côté esclave du pseudo-terminal).

Et puis, il y a un autre mode dans lequel le pilote du terminal est invité à ne pas faire écho, mais l'application cette fois génère quelque chose. L'application (comme ceux qui utilisent readline comme gdb, bash ...) peut l'envoyer sur sa sortie standard ou stderr, ce qui sera difficile à différencier de quelque chose qu'elle génère pour autre chose que de renvoyer en écho les données entrées par l'utilisateur.

Ensuite, pour différencier la sortie standard d’une application de sa position standard, plusieurs approches sont possibles.

Beaucoup d'entre eux impliquent de rediriger les commandes stdout et stderr vers des tubes et ces tubes lus par une application pour le colorer. Cela pose deux problèmes:

  • Une fois que stdout n'est plus un terminal (à la place d'un tuyau), de nombreuses applications ont tendance à adapter leur comportement pour commencer à mettre en tampon leur sortie, ce qui signifie que la sortie sera affichée en gros morceaux.
  • Même si c'est le même processus qui traite les deux canaux, il n'y a aucune garantie que l'ordre du texte écrit par l'application sur stdout et stderr sera conservé, car le processus de lecture ne peut pas savoir (s'il y a quelque chose à lire dans les deux) faut-il commencer à lire à partir du tuyau "stdout" ou du tuyau "stderr".

Une autre approche consiste à modifier l’application afin qu’elle colore ses stdout et stdin. Il est souvent impossible ou réaliste de le faire.

Ensuite, une astuce (pour les applications liées dynamiquement) peut être de détourner (en utilisant $LD_PRELOADcomme dans la réponse de sickill ) les fonctions de sortie appelées par l'application pour produire quelque chose et y inclure du code qui définit la couleur de premier plan en fonction du type de sortie. sur stderr ou stdout. Cependant, cela signifie qu’il faut détourner toutes les fonctions possibles de la bibliothèque C et de toute autre bibliothèque qui effectue un write(2)appel système directement appelé par l’application et qui pourrait éventuellement écrire quelque chose sur stdout ou stderr (printf, met, perror ...), et même alors. , cela peut modifier son comportement.

Une autre approche pourrait consister à utiliser les astuces de PTRACE straceou à les gdbutiliser pour se raccrocher chaque fois que l' write(2)appel système est appelé et définir la couleur de sortie en fonction du type write(2)de descripteur de fichier 1 ou 2.

Cependant, c'est une très grosse chose à faire.

Une astuce que je viens de jouer consiste à détourner stracelui-même (qui fait le sale boulot de s’accrocher avant chaque appel système) en utilisant LD_PRELOAD, pour lui dire de changer la couleur de sortie en fonction du fait qu’il a détecté un write(2)fd 1 ou 2

En regardant stracele code source, nous pouvons voir que tout ce qu’il génère est réalisé via la vfprintffonction. Tout ce que nous devons faire est de détourner cette fonction.

Le wrapper LD_PRELOAD ressemblerait à ceci:

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

Ensuite, nous le compilons avec:

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

Et utilisez-le comme:

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

Vous remarquerez que, si vous remplacez some-cmdpar bash, l'invite bash et ce que vous tapez apparaissent en rouge (stderr) et avec zshen noir (car zsh copie stderr sur un nouveau fd pour afficher son invite et son écho).

Cela semble fonctionner étonnamment bien, même pour les applications auxquelles vous vous attendez (comme celles qui utilisent des couleurs).

Le mode de coloration est affiché sur stracele stderr qui est supposé être le terminal. Si l'application redirige son stdout ou stderr, notre strace détourné continuera à écrire les séquences d'échappement colorées sur le terminal.

Cette solution a ses limites:

  • Celles inhérentes à strace: les problèmes de performances, vous ne pouvez pas exécuter d’autres commandes PTRACE telles que straceou gdbdans celle-ci, ni les problèmes de setuid / setgid
  • Sa coloration est basée sur le writes sur stdout / stderr de chaque processus. Ainsi, par exemple, dans sh -c 'echo error >&2', errorserait vert car le echosort sur sa sortie standard (lequel sh est redirigé vers le statut de sh, mais tout ce que strace voit est a write(1, "error\n", 6)). Et in sh -c 'seq 1000000 | wc', seqfait beaucoup ou writes sur sa sortie standard, le wrapper finira par envoyer beaucoup de séquences d'échappement (invisibles) au terminal.
Stéphane Chazelas
la source
Agréable. Il y avait des suggestions de wrappers préexistants sur la question en double . J'ai signalé la question de la fusion afin que votre réponse soit visible là-bas.
Gilles 'SO- arrête d'être méchant'
Peut-être peaufiner la syntaxe vim? strace $CMD | vim -c ':set syntax=strace' -.
Pablo Un
4

Voici une preuve de concept que j'ai faite il y a longtemps.

Cela ne fonctionne que dans 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

Il suppose également que vous avez une fonction appelée setcolor.

Une version simplifiée:

setcolor()
{
    case "$1" in
    red)
        tput setaf 1
        ;;
    normal)
        tput sgr0
        ;;
    esac
}
Mikel
la source
Il y a une façon beaucoup plus simple à faire: exec 2> >(rederr). Les deux versions présenteront les problèmes mentionnés dans ma réponse, à savoir réorganiser les lignes et risquer des sorties endommagées (en particulier avec les longues lignes).
Gilles 'SO- arrête d'être méchant'
J'ai essayé ça et ça n'a pas marché.
Mikel
seterrdevrait être un script autonome, pas une fonction.
Gilles 'SO- arrête d'être méchant'
4

Voir Mike Schiraldi Hilite qui le fait pour une commande à la fois. Mon propre gush fait cela pour toute une session, mais a aussi beaucoup d'autres fonctionnalités / idiosyncracies que vous pourriez ne pas vouloir.

Colin Macleod
la source