Coloriser la sortie d'un programme

Développant sur Google Appengine en ce moment, en python, j'utilise les logs à foison. Ces logs sont pratique et ont un niveau d'information (WARNING, INFO, ERROR...). Mais la lisibilité n'est pas forcément adaptée, car en effet les logs sont simplement des lignes de texte. C'est alors qu'une idée m'a frappée (sans gravité, j'ai pas eut mal): et si je colorisai la sortie. Ma méthode fonctionne pour à peu près tous les programmes que vous utilisez dans un terminal, et peut être adaptée à pas mal de situations

L'idée est simple, il suffit de rediriger les sorties du programme dans un descripteur qui va modifier la sortie. Avant tout, laissez moi vous expliquer comment coloriser du texte dans un terminal (si vous ne savez pas comment), puis comment utiliser `sed` avec la subtilité. Enfin, on va passer par une commande `exec` qui va opérer notre transformation.

Premier point, les couleurs dans un terminal.

Bash (ou d'autres shell) permet de coloriser une chaine de caractères. Le principe étant de donner un caractère spécial, puis un code couleur, puis la chaine.

Ainsi, pour coloriser en rouge, en entre la séquence "\033[31m" puis la chaine. Il faut juste comprendre cela:

  • \033 => caractère qui prévient qu'on va changer de couleur, de poids de fonte...
  • [ => on va donner une couleur
  • 33 => code couleur du rouge
  • m => terminé

Cette séquence retourne un caractère spécial c'est important de bien le comprendre, car en réalité toute la subtilité de ce que nous allons faire repose sur ce principe. Ce caractère est invisible, il est interprété par le terminal pour changer la couleur.

Cela ne suffit pas, il faudra que le terminal sache gérer les caractères échapés, je parle du "\033". La commande "echo" sait bien le faire:

echo -e "\033[31mEt hop une couleur"

Le souci, c'est que là votre console passe en rouge... et ne revient pas au mode "normal". Pour cela, on utilise la couleur "0"

echo -e "\033[0mOn revient à la normale"

Pour faire plus simple, on peut directement activer la couleur, entrer la chaine, puis revenir à la normale:

echo -e "\033[31mHoulla une erreur en rouge\033[0m"

Les couleurs qu'on va utiliser:

  • 31 rouge => pour les erreurs
  • 33 orange => pour les warnings
  • 34 bleu => pour les infos

Bien, passons à `sed`. Cet outil permet de faire des opération sur du texte assez facilement en utilisant une syntaxe proche du perl. Ainsi, la commande "s" de sed permet de "substituer" du texte, comprennez "le remplacer"

Dans une commande sed "s", le caractère "&" correspond à la chaine trouvée. Ainsi, pour remplacer "WARNING" en "toto-WARNING-fin" on fera:

echo "WARNING there is an error" | sed 's/WARNING/toto-&-fin/'
#affiche: toto-WARNING-fin there is an error

Bon, vous avez compris, il faut remplacer maintenant "WARNING" en "\033[33mWARNING\033[0m". Sauf que voilà, sed ne sait pas utiliser le mode "échappé". Cela n'est rien, on va récupérer le caractère que retourne "echo -e" pour le placer dans la chaine de remplacement. Voilà un exemple:

NORMAL=$(echo -e "\033[0m")
ORANGE=$(echo -e "\033[33m")

echo "WARNING there is an error" | sed "s/WARNING/$ORANGE&$NORMAL/"

Simple en fait, relisez bien vous allez comprendre.

Bien, reste à faire ça pour toutes les lignes de sortie d'un programme. Reprenons notre exemple avec GAE, j'ai des message "WARNING, INFO et ERROR".

Le souci, c'est que la sortie est "en continue", mais on va utiliser une commande qui va gérer une redirection de flux: exec

Si vous ne le savez pas, un shell utilise des descripteurs connus: 1 = sortie standard, 2 = sortie d'erreur. Si je redirige une sortie dans un de ces flux, il sera traité de différente manières.

Pour rediriger dans un flux, on utilise la séquence "N>&M" où N est le flux d'entrée, et M le flux où on redirige. Ainsi:

#on redirige la sortie normale dans le flux d'erreur
echo "pouet pouet" 1>&2 
#1 est utilisé par défaut, donc cela fait la même chose:
echo "pouet pouet" >&2 

#Par exemple
echo "pouet pouet"  2>fichier_erreur 1>fichier_normal 1>&2
#ici, le fichier_normal ne contien rien, par contre fichier_erreur 
#contient pouet pouet

OK, même si cela reste obscure pour certains, ne paniquez pas... suivez juste le reste de mon article.

"exec" peut ouvrir un descripteur, simplement en lançant "exec 3> ..." On aura alors un nouveau flux portant le numéro 3.

Donc:

exec 3>fichier
echo "pouet pouet" >&3

ici, on redirige le flux 3 dans un fichier, donc en écrivant dans le flux, on écrit dans un fichier...

Autre subtilité des shells: ">(une commande)" (surtout pas d'espace après le >) la commande entre parenthèse est considéré comme un fichier

Ainsi:

exec 3> >(sed 's/toto/pouet/g')
echo "j'ai pas toto mais j'aime truc" >&3
echo "toto toto :)" >&3

Vous comprennez ce qu'il se passe: tout ce que j'écris dans le "descripteur 3" passe dans la commande "sed..." Ha... on y arrive !

NORMAL=$(echo -e "\033[0m")
ORANGE=$(echo -e "\033[33m")
RED=$(echo -e "\033[31m")
BLUE=$(echo -e "\033[34m")

exec 3> >(sed "s/WARNING/$ORANGE&$NORMAL/g;   s/ERROR/$RED&$NORMAL/g; s/INFO/$BLUE&$NORMAL/g" )

ma_commande >&3

Et voilà ! Toute mes sorties de "ma_commande" passerons dans le flux "3" et on verra un remplacement de mot par ce même mot en couleur.

Si "ma_commande" utilise les flux de sortie d'erreur, vous pouvez carrément faire:

ma_commande 1>&2 2>&3

Alors, ici, on utilise des couleurs, mais vous pouvez utiliser cette technique pour mettre des balises XML, mettre le texte en minscule, ou rediriger dans d'autre descripteurs... en gros la méthode est ouverte à pas mal de variantes.

Voilà pour aujourd'hui !