Comment un programme décide-t-il d’avoir ou non une sortie colorée?

17

Lorsque j'exécute une commande à partir d'un terminal qui imprime une sortie colorée (comme lsou gcc), la sortie colorée est imprimée. D'après ma compréhension, le processus génère en fait des codes d'échappement ANSI et le terminal formate la couleur.

Cependant, si j'exécute la même commande par un autre processus (disons une application C personnalisée) et redirige la sortie vers la propre sortie de l'application, ces couleurs ne persistent pas.

Comment un programme décide-t-il de produire ou non du texte au format couleur? Y a-t-il une variable d'environnement?

Chris Smith
la source

Réponses:

25

La plupart de ces programmes ne fournissent par défaut que des codes couleur à un terminal; ils vérifient si leur sortie est un ATS, en utilisant isatty(3). Il existe généralement des options pour remplacer ce comportement: désactiver les couleurs dans tous les cas ou activer les couleurs dans tous les cas. Pour GNU greppar exemple, --color=neverdésactive les couleurs et les --color=alwaysactive.

Dans un shell, vous pouvez effectuer le même test en utilisant l' -t testopérateur: [ -t 1 ]ne réussira que si la sortie standard est un terminal.

Stephen Kitt
la source
Existe-t-il un moyen de tromper l'application démarrée que le processus est un tty?
Chris Smith
4
Demandé et répondu à unix.stackexchange.com/questions/249723 déjà, chris13523. Soit dit en passant, les commentaires ne sont pas vraiment le lieu de questions complémentaires.
JdeBP
1
@ chris13524 voir le lien de JdeBP; vous pouvez également forcer des programmes à produire des codes couleur dans de nombreux cas (voir ma réponse mise à jour).
Stephen Kitt
13

Y a-t-il une variable d'environnement?

Oui. C'est la TERMvariable d'environnement. En effet, plusieurs éléments sont utilisés dans le cadre du processus de décision.

Il est difficile de généraliser ici, car tous les programmes ne s'entendent pas sur un seul organigramme de décision. En fait, GNU grep, mentionné dans la réponse de M. Kitt, est un bon exemple de valeur aberrante qui utilise un processus de décision quelque peu inhabituel avec des résultats inattendus. En termes très généraux, donc:

  • La sortie standard doit être un périphérique terminal, comme déterminé par isatty().
  • Le programme doit pouvoir rechercher l'enregistrement du type de terminal dans la base de données termcap / terminfo.
  • Il doit donc y avoir un type de terminal à rechercher. La TERMvariable d'environnement doit exister et sa valeur doit correspondre à un enregistrement de base de données.
  • Il doit donc y avoir une base de données terminfo / termcap. Sur certaines implémentations du sous-système, l'emplacement de la base de données termcap peut être spécifié à l'aide d'une TERMCAPvariable d'environnement. Ainsi, sur certaines implémentations, il existe une deuxième variable d'environnement.
  • L'enregistrement termcap / terminfo doit indiquer que le type de terminal prend en charge les couleurs. Il y a un max_colorschamp dans terminfo. Il n'est pas défini pour les types de terminaux qui n'ont pas réellement de capacités de couleur. En effet, il existe une convention terminfo selon laquelle pour chaque type de terminal à colorier, il existe un autre enregistrement avec -mou -monoajouté au nom qui n'indique aucune capacité de couleur.
  • L'enregistrement termcap / terminfo doit permettre au programme de changer les couleurs. Il y a des champs set_a_foregroundet set_a_backgrounddans terminfo.

C'est un peu plus complexe que de simplement vérifier isatty(). Il est rendu encore plus compliqué par plusieurs choses:

  • Certaines applications ajoutent des options de ligne de commande ou des indicateurs de configuration qui remplacent la isatty()vérification, de sorte que le programme suppose toujours ou jamais qu'il a un terminal (à colorier) comme sortie. Pour des exemples:
    • GNU lsa l' --coloroption de ligne de commande.
    • BSD lsexamine les variables d'environnement CLICOLOR(son absence signifie jamais ) et CLICOLOR_FORCE(sa présence signifie toujours ), ainsi que l' -Goption de ligne de commande.
  • Certaines applications n'utilisent pas termcap / terminfo et ont des réponses câblées à la valeur de TERM.
  • Tous les terminaux n'utilisent pas les séquences ECMA-48 ou ISO 8613-6 SGR, qui sont légèrement mal nommées "séquences d'échappement ANSI", pour changer les couleurs. Le mécanisme termcap / terminfo est en fait conçu pour isoler les applications de la connaissance directe des séquences de contrôle exactes. (De plus, il y a un argument à faire valoir que personne n'utilise les séquences SGR ISO 8613-6, parce que tout le monde est d'accord sur le bug de l'utilisation du point-virgule comme délimiteur pour les séquences SGR de couleur RVB. La norme spécifie en fait deux points.)

Comme mentionné, GNU grepprésente en fait certaines de ces complexités supplémentaires. Il ne consulte pas termcap / terminfo, câblé les séquences de contrôle à émettre et câblant une réponse à la TERMvariable d'environnement.

Le port Linux / Unix de celui-ci a ce code , qui permet la coloration uniquement lorsque la TERMvariable d'environnement existe et que sa valeur ne correspond pas au nom câblé dumb:

int
should_colorize (void)
{
  char const * t = getenv ("TERM");
  retourne t && strcmp (t, "idiot")! = 0;
}

Donc, même si TERMc'est le cas xterm-mono, GNU grepdécidera d'émettre des couleurs, même si d'autres programmes comme celui- vimci ne le feront pas.

Le port Win32 de celui-ci a ce code , qui permet la coloration soit lorsque la TERMvariable d'environnement n'existe pas ou lorsqu'elle existe et que sa valeur ne correspond pas au nom câblé dumb:

int
should_colorize (void)
{
  char const * t = getenv ("TERM");
  revenir ! (t && strcmp (t, "idiot") == 0);
}

grepProblèmes de GNU avec la couleur

grepLa colorisation de GNU est en fait notoire. Parce qu'il ne fait pas vraiment un bon travail de construction de la sortie du terminal, mais qu'il blâme plutôt dans quelques séquences de contrôle câblées à divers points de sa sortie dans le vain espoir que cela soit assez bon, il affiche en fait une sortie incorrecte dans certaines circonstances.

Dans ces circonstances, il doit coloriser quelque chose qui se trouve à la marge droite du terminal. Les programmes qui font correctement la sortie du terminal doivent tenir compte des marges droites automatiques. En plus de la légère possibilité que le terminal ne les ait pas (à savoir le auto_right_marginchamp dans terminfo), le comportement des terminaux qui ont des marges droites automatiques suit souvent le précédent DEC VT du retour à la ligne en attente . GNU grepne tient pas compte de cela, s'attendant naïvement à un retour à la ligne immédiat , et sa sortie colorée va mal.

La sortie couleur n'est pas une chose simple.

Lectures complémentaires

JdeBP
la source
2
Si je comprends bien, l'OP pose des questions sur le changement de comportement lorsque la sortie est redirigée; $TERMn'explique pas cela. (Votre réponse est intéressante en général, mais je ne pense pas qu'elle réponde à la question ...)
Stephen Kitt
très intéressant. Cela fait maintenant quelques mois que je voulais un aperçu comme celui-ci sur la façon dont les programmes découvrent (ou simplement «décident») ce que sont les capacités d'un terminal. Cela donne également un aperçu de la raison pour laquelle il est si difficile de trouver un aperçu comme celui-ci - parce que chaque programme semble le faire légèrement différemment.
the_velour_fog
Une explication de ce que signifie un retour à la ligne en attente et un retour à la ligne immédiat, ainsi qu'un exemple démontrant la différence, seraient bien.
mosvy
0

La unbuffercommande du package attendu dissocie la sortie du premier programme et l'entrée du deuxième programme.

Vous l'utiliseriez comme ceci:

unbuffer myshellscript.sh | grep value

Je l'utilise tout le temps avec ansible et un script ctee homebrewed afin que je puisse voir la sortie couleur sur le terminal, tout en laissant le fichier journal avec une sortie normale (non colorisée).

unbuffer ansible-playbook myplaybook.yml | ctee /var/log/ansible/run-$( date "+%F" ).log
bgStack15
la source