Colorer le texte du terminal : tput et séquences ANSI

Table des matières

Un terminal en noir sur blanc ou inversement, ce n’est ni très intéressant, ni très joli, ni très informatif. Heureusement, les émulateurs de terminal modernes offrent une variété d’options de formatage et de couleurs de texte et de fond. Il faut juste savoir les appliquer à l’aide des séquences d’échappement ANSI ou de la commande tput. Dans cet article, nous explorerons ces deux méthodes.

Comment un terminal formate du texte

Le terminal, c’est une interface : sa seule véritable fonction, c’est d’afficher du texte. Il imprime tout ce qui lui provient de l’entrée standard, de la sortie standard ou de la sortie d’erreur. Typiquement, c’est le shell qui lui fournit une invite de commandes à imprimer, puis, lorsque l’utilisateur tape une commande, il imprime chaque caractères saisi, un à un. A chaque commande exécutée, le terminal pourra recevoir une sortie à imprimer de la part du programme exécuté, quel qu’il soit. Un article précédent explique la différence entre un terminal, une console et un shell.

Afin de pouvoir imprimer quoique ce soit, il lui faut une police et une couleur qui contraste avec sa couleur de fond. De plus, ses utilisateurs apprécient beaucoup l’option de pouvoir afficher du texte dans une couleur ou un style de police différent. Pouvoir afficher un message d’erreur critique en rouge ou simplement souligner un lien, par exemple, c’est très utile.

En vérité, le terminal ne décide pas lui-même des styles de police et des couleurs qu’il affiche. Celles qu’il utilisent par défaut, il les hérite de son thème ou de celui du système d’exploitation. Les couleurs de l’invite des commandes, s’il y en a, lui sont indiquées par le Shell. Toutes autres couleurs lui proviennent soit du Shell, soit des programmes qui lui demandent d’imprimer du texte.

Comme le terminal gère uniquement du texte, les programmes qui souhaitent modifier la façon dont il imprime un texte doivent trouver un moyen de lui communiquer le formatage souhaité dans le texte lui-même. C’est l’un des buts des séquences d’échappement ANSI ainsi que de la commande tput.

Qu’est-ce qu’une séquence d’échappement ANSI ?

Les séquences d’échappement ANSI sont un standard qui permettent de communiquer non seulement les couleurs et les styles de police, mais aussi la position du curseur et bien d’autres options au terminal. Pour encoder ces informations, on peut incorporer une séquence de caractères distincte directement dans le texte qu’on lui envoie. Le terminal interprète ces séquences non comme des caractères à imprimer, mais comme des commandes.

Une séquence d’échappement ANSI commence toujours avec un caractère d’échappement, qui peut s’écrire de trois façons :

\e
\033
\x1B

En consultant une table ASCII, on se rend compte que ces trois notations correspondent toutes littéralement au 27ème caractère ASCII nommé ESC (“escape” ou “échappe”). La première est sa notation textuelle, la seconde, c’est son nombre octal et la troisième, son nombre hexadécimal.

Dans la majorité des cas, ce code d’échappement est suivi d’un crochet ouvrant “[” . Ensemble, ces deux octets forment le CSI : “control sequence introducer”, ou “introducteur de séquence de contrôle” en français.

Voici la structure complète d’une séquence de contrôle ANSI :

"\e" + "[" + <nombres séparés de ";" (optionnel)> + <lettre>

On peut penser à une séquence de contrôle comme à un appel de fonction où la lettre à la fin est le nom de la fonction et les nombres optionnels au milieu sont ses paramètres. On peut donc lire la séquence " \e[1;42m" comme ceci :

\e[    # appel de fonction
1;42   # paramètres de la fonction (1, 42)
m      # nom de la fonction

On peut utiliser ces séquences d’échappement ANSI pratiquement partout, dans nos programmes, mais aussi en ligne de commande. Pour utiliser une séquence d’échappement ANSI dans une commande echo par exemple, on doit utiliser l’option -e pour qu’elle soit interprétée :

echo -e "L'option -e permet d'interpréter \e[1m<--ceci et cela-->\e[0m"

Nous verrons plus loin quels sont les séquences d’échappement qui peuvent affecter l’affichage d’un texte.

Comment marche la commande tput ?

Sur la ligne de commande ou dans un script Bash, on préférera peut-être utiliser la commande tput pour indiquer l’affichage d’un texte. Cette commande fait usage du terminfo du terminal pour automatiquement trouver la séquence d’échappement appropriée. Le terminfo, c’est une base de donnée qui décrit les capacités de notre terminal, quelles opérations il sait faire, comment les effectuer et quelles séquences d’initialisation il lui faut. Pour voir le contenu de ce terminfo, on peut utiliser la commande infocmp.

Chacune des capacités du terminal porte un nom. Par exemple, bold décrit la capacité du terminal à mettre du texte en gras. On peut donc indiquer à une commande echo de mettre en valeur un texte comme ceci, par exemple :

echo "Ce texte est $(tput bold)important"

La commande tput va alors chercher dans le terminfo quelle séquence utiliser pour mettre le texte en gras. Evidemment, les séquences sont standardisés avec les séquences d’échappement ANSI. Toutefois, tput a deux avantages : il peut exécuter la capacité même si le terminal en question ne se conforme pas au standard ANSI et il nous informe si le terminal n’a pas la capacité demandée.

En effet, chaque terminal est différent et tous n’ont pas les mêmes capacités. La plupart auront la faculté d’imprimer un texte avec les styles de police élémentaires comme en gras ou en italique, ou avec une couleur rudimentaire comme le rouge, le vert et le bleu. Mais beaucoup ne prennent pas en charge les attributs typographiques peu utilisés comme le barré ou le clignotant. Et certains ne connaissent que le noir et le blanc ! Pour voir combien de couleurs un terminal peut afficher, on peut utiliser la commande tput colors.

Modifier le style d’un texte dans le terminal

Comme on l’a vu, on peut contrôler la façon dont le terminal affichage d’un texte à l’aide de la commande tput ou d’une séquence d’échappement ANSI. Les tableaux ci-dessous décriront les codes pour ces deux méthodes.

En ce qui concerne la séquence ANSI, la lettre qu’on indiquera à la fin est le “m”, qui signale que l’on souhaite contrôler un mode graphique, c’est à dire un formatage. Si l’on voulait contrôler la position du curseur, par exemple, on utiliserait, plutôt la lettre “H” qui correspond à la capacité cup dans le terminfo. Plusieurs autres lettres existent pour contrôler toutes autres sortes d’options dans le terminal, mais cet article porte exclusivement sur le formatage du texte lui-même.

Modifier le style de police

On peut donner plusieurs attributs typographiques à un texte pour contrôler le style de police. Le tableau ci-dessous montre à la fois le code ANSI à insérer dans la séquence d’échappement et la capacité du terminfo (“TermCap”) à indiquer à la commande tput pour avoir l’effet indiqué.

Code ANSI TermCap Description Exemple
0 sgr0 Remet tous les attributs à zéro
1 bold Gras ou intensité accrue
2 dim Intensité réduite
3 sitm Italique
4 smul Souligné
5 blink Clignotant
6 blink Clignotant
7 rev Inversé
8 invis Invisible
9 smxx Barré

Pour avoir un texte en gras, la séquence d’échappement ANSI sera donc : \e[1m. On peut reproduire le même effet avec la commande tput des deux façons suivantes :

tput bold && echo coucou
echo "$(tput bold) coucou"

Peut-on donc combiner tous ces attributs pour créer un texte en gras, de faible intensité, italique, clignotant, en inversé, souligné et barré ? C’est un peu excessif, mais oui, c’est tout à fait possible de faire tout ça en une seule séquence d’échappement ANSI, en séparant les attributs avec des points-virgule :

echo -e "\e[0;1;2;3;4;5;6;7;9mCoucou\e[0m"

Inutile de préciser qu’on ne peut pas inclure l’attribut “invisible” (8) si on souhaite voir tous les autres effets de texte. Par contre, l’invisible (8) se combine bien avec l’attribut inversé (7) pour mettre en valeur le fait qu’il y a du texte mais qu’il est rétracté, par exemple.

L’ordre dans lequel on fournit les attributs n’importe pas, sauf pour l’attribut 0. L’attribut 0 remet tous les autres attributs de style à zéro pour revenir à un texte normal. Evidemment, si on met le 0 dans notre exemple précédent avant le “m” et après tous les autres attributs, ils seront annulés ! Cela dit, il est important de ne pas oublier d’indiquer la séquence d’échappement \e[0m à la fin de notre ligne. Sinon, les changements de styles continueront probablement à affecter toutes les lignes suivantes du terminal, jusqu’à ce que le terminal rencontre un \e[0m.

La commande tput a souvent le même effet : il ne faut pas oublier de faire un tput sgr0 pour annuler tous les changements de style. Par contre, il est un peu plus compliqué de lui indiquer plusieurs changements d’attributs à la fois. Par exemple, pour donner à un texte tous les attributs comme précédemment, il faudra faire plusieurs appels à la suite :

tput bold && tput dim && tput sitm && tput smul && tput blink && tput rev && tput smxx && echo coucou

Pour pouvoir demander plusieurs changements en un seul appel de commande, il faut lui indiquer l’option -S. Ensuite, il faut lui fournir une liste d’attributs, un par ligne, et terminée par un point d’exclamation, dans l’entrée standard avant d’obtenir le résultat :

$ echo "$(tput -S)coucou$(tput sgr0)"
> bold
> dim
> sitm
> smul
> blink
> rev
> smxx
> !

Changer la couleur du texte

Le tableau ci-dessous présente des codes ANSI et les noms de capacité terminfo les plus communs pour contrôler la couleur du texte lui-même.

Code ANSI TermCap Couleur Exemple
30 setaf 0 Noire
31 setaf 1 Rouge
32 setaf 2 Verte
33 setaf 3 Jaune ou marron
34 setaf 4 Bleue
35 setaf 5 Violette ou rose
36 setaf 6 Cyan
37 setaf 7 Grise

Ces couleurs ont aussi des variantes plus vives. Les codes ANSI pour ces variantes vont de 90 à 97, et leurs équivalents en capacité dans le terminfo sont les setaf 8 à 15. Cette appellation " setaf" est une abréviation de “set a foreground”, qui signifie en français “définir la couleur d’avant-plan”, c’est à dire la couleur du texte.

Evidemment, on peut combiner une couleur de texte avec un style de police comme le gras ou l’italique. On le fait de la même manière qu’on a vu plus haut, il suffit d’ajouter l’attribut de couleur à tous les autres attributs de texte. Par exemple, \e[34;4m (ou \e[4;34m) affichera le texte en bleu et en souligné. Il est évidemment impossible de cumuler les couleurs, seule la dernière couleur spécifiée sera retenue pour l’affichage. La séquence \e[0m annule aussi tout attribut de couleur.

La couleur que le terminal affiche varie d’un terminal à l’autre, particulièrement en ce qui concerne les couleurs intermédiaires. Les couleurs primaires, le rouge, le vert et le bleu sont habituellement identiques d’un terminal à l’autre. Cependant, le violet en particulier ressemble parfois plus à du rose et le jaune peut plutôt avoir l’air marron.

Avec les séquences d’échappement ANSI, il est toutefois possible d’indiquer une couleur RGB très précise, à condition que le terminal puisse afficher 256 couleurs (voir le nombre de couleurs que le terminal peut afficher avec la commande tput colors ou tput longname).

Code ANSI Description
38;2;r;g;b Définit une couleur de texte selon des valeurs RGB. Par exemple : \e[38;2;255;0;255m

Je n’ai pas trouvé d’équivalent dans les capacités du terminfo. Que celui qui trouve laisse un commentaire ! Je serai ravie d’apprendre quelle capacité permet d’indiquer une couleur RGB avec la commande tput.

Afficher une couleur de fond

La méthode pour appliquer une couleur de fond est la même que pour la couleur du texte. Voici le tableau des codes ANSI pour les couleurs de fond et de leurs équivalents en capacités dans le terminfo.

Code ANSI TermCap Couleur Exemple
40 setab 0 Noire
41 setab 1 Rouge
42 setab 2 Verte
43 setab 3 Jaune ou Marron
44 setab 4 Bleue
45 setab 5 Violette ou Rose
46 setab 6 Cyan
47 setab 7 Grise

Tout comme les couleurs de texte, il existe des variantes plus vives pour les couleurs de fond. Pour les séquences d’échappement ANSI, les codes pour ces couleurs vives vont de 100 à 107, et l’équivalent dans le terminfo, c’est setab 8 à 15. L’appellation " setab" est aussi une abréviation, cette fois de “set a background”, qui signifie en français “définir la couleur de fond”.

On peut évidemment aussi combiner une couleur de fond avec une couleur de texte et des attributs de style. Par exemple, \e[1;34;3;43;4;5m affichera le texte en bleu sur jaune, en gras, italique, souligné et clignotant. Magnifique ! Par contre, seule la dernière couleur de fond spécifiée sera affichée : impossible de combiner les couleurs de fond entre elles. Comme tous les autres attributs de texte, la couleur de fond est annulée dès qu’une autre couleur de fond la remplace ou entièrement avec la séquence \e[0m.

De la même manière que les couleurs de texte, on peut spécifier une couleur RGB particulière pour le fond avec nos séquences d’échappement ANSI :

Code ANSI Description
48;2;r;g;b Définit une couleur de fond selon des valeurs RGB. Par exemple : \e[48;2;255;0;255m

Le nom de la capacité équivalente dans le terminfo m’échappe aussi pour cette fonctionnalité.

Afficher les capacités de formatage du terminal

On l’a vu, les émulateurs de terminal n’ont pas tous les mêmes capacités. En ce qui concerne les couleurs, la plupart des émulateurs de terminal peuvent afficher au moins 8 couleurs de texte et 8 couleurs de fond. Certains proposent jusqu’à 256 couleurs, et d’autres ne peuvent afficher que du blanc et du noir.

Pour ce qui est des styles de police, la plupart des émulateurs auront au moins la capacité de mettre un texte en gras, en l’italique, en invisible et en couleurs inversées. Cependant, les séquences d’échappement ANSI n’ont pas non plus exactement le même effet dans différents terminaux : ce qui se comporte comme du gras sur un terminal se comporte plutôt comme la projection d’une forte intensité sur l’autre. Dans le premier terminal, l’attribut de gras sera donc compatible avec l’attribut de faible luminosité mais pas dans le second.

Alors comment peut-on voir quelles capacités de formatage de texte notre terminal possède et comment il les affiche ? Pour afficher le comportement des mêmes séquences ANSI sur différents terminaux, il suffit d’écrire un petit programme, comme celui-ci en C :

#include <stdio.h>

int main(void)
{
 int n;
 for (int i = 0; i < 10; i++) {
  for (int j = 0; j < 10; j++) {
   n = (10 * i) + j;
   printf("\e[%dm  %3d  \e[0m", n, n);
  }
  printf("\n");
 }
 return (0);
}

Les images ci-dessous sont le résultat de ce petit programme dans le terminal GNOME sous Ubuntu. Elles illustrent les effets de toutes les séquences d’échappement ANSI qui concernent le formatage de texte. Étonnamment, on découvre ici que dans ce terminal, la séquence \e[21m souligne le texte deux fois !

Tableau de codes de couleur et de formatage utilisables dans une séquence d'échappement ANSI, affiché dans un terminal de thème sombre sur fond noir.
Tableau des codes de formatage des séquences d’échappement ANSI dans le terminal GNOME sous Ubuntu (thème sombre)
Tableau de codes de couleur et de formatage utilisables dans une séquence d'échappement ANSI, affiché dans un terminal de thème clair sur fond blanc.
Tableau des codes de formatage des séquences d’échappement ANSI dans le terminal GNOME sous Ubuntu (thème clair)

En comparant les deux résultats, on se rend compte que le thème du terminal, clair ou sombre, a un effet sur la lisibilité de certaines combinaisons de couleur. Sur un terminal sombre, le texte de couleur noire (30) n’est pas du tout visible. Sur le terminal clair, les couleurs de texte grises (37 et 97) sont difficiles à lire. Il faut donc se restreindre un peu si l’on souhaite indiquer une couleur spécifique à la sortie d’un programme qu’on souhaite distribuer à d’autres utilisateurs. Ils n’utiliseront pas tous le même thème que nous !


C’est tout pour la modification du style du texte dans le terminal ! Si tu connais d’autres façons de contrôler l’affichage du texte dans le terminal, n’hésites pas à laisser un commentaire.

Sources et lectures supplémentaires

Commentaires

Articles connexes

Binaire 010 : l'utilité des opérations bitwise et du bit shifting

Les ordinateurs ne connaissent qu’une seule langue : le binaire. Nos nombreux langages de programmation nous permettent de donner des instructions dans un format lisible par l’humain.

Lire la suite

Threads, mutex et programmation concurrente en C

Par souci d’efficacité ou par nécessité, un programme peut être construit de façon concurrente et non séquentielle.

Lire la suite

Programmation réseau via socket en C

Dans ce monde informatique ultra-connecté, il est crucial de savoir comment envoyer et recevoir des données à distance, grâce aux sockets.

Lire la suite