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_PRELOAD
comme 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 strace
ou à les gdb
utiliser 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 strace
lui-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 strace
le code source, nous pouvons voir que tout ce qu’il génère est réalisé via la vfprintf
fonction. 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-cmd
par bash
, l'invite bash et ce que vous tapez apparaissent en rouge (stderr) et avec zsh
en 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 strace
le 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 strace
ou gdb
dans celle-ci, ni les problèmes de setuid / setgid
- Sa coloration est basée sur le
write
s sur stdout / stderr de chaque processus. Ainsi, par exemple, dans sh -c 'echo error >&2'
, error
serait vert car le echo
sort 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'
, seq
fait beaucoup ou write
s sur sa sortie standard, le wrapper finira par envoyer beaucoup de séquences d'échappement (invisibles) au terminal.