Comment un programme sait-il si la sortie standard est connectée à un terminal ou à un tuyau?


12

J'ai du mal à déboguer un programme de segfault car la sortie juste avant le segfault est ce dont j'ai besoin, mais cela est perdu si je redirige la sortie vers un fichier. Selon cette réponse: /unix//a/17339/22615 , c'est parce que le tampon de sortie du programme se vide immédiatement lorsqu'il est connecté à un terminal mais uniquement à certains points lorsqu'il est connecté à un tuyau. Quelques questions ici:

  • Comment un programme détermine-t-il à quoi sa sortie standard est connectée?

  • Comment la commande "script" produit-elle le même comportement que lorsque le programme écrit sur un terminal?

  • Peut-on y parvenir sans la commande de script?


Une question connexe est unix.stackexchange.com/q/513926/5132 .
JdeBP

Réponses:


23

Dire si un descripteur de fichier pointe vers un terminal

Un programme peut dire si un descripteur de fichier est associé à un périphérique tty en utilisant la isatty()fonction C standard (qui généralement fait en dessous un ioctl()appel système inoffensif spécifique au tty qui retournerait avec une erreur lorsque le fd ne pointe pas vers un périphérique tty) .

L' utilitaire [/ testpeut le faire avec son -topérateur.

if [ -t 1 ]; then
  echo stdout is open to a terminal
fi

Tracer les appels de la fonction libc sur un système GNU / Linux:

$ ltrace [ -t 1 ] | cat
[...]
isatty(1)                                      = 0
[...]

Suivi des appels système:

$ strace [ -t 1 ] | cat
[...]
ioctl(1, TCGETS, 0x7fffd9fb3010)        = -1 ENOTTY (Inappropriate ioctl for device)
[...]

Dire s'il pointe vers un tuyau

Pour déterminer si un fd est associé à un tube / fifo, on peut utiliser l' fstat()appel système , qui renvoie une structure dont le st_modechamp contient le type et les autorisations du fichier ouvert sur ce fd. La S_ISFIFO()macro C standard peut être utilisée sur ce st_modechamp pour déterminer si le fd est un tube / fifo.

Il n'y a aucun utilitaire standard qui peut faire un fstat(), mais il existe plusieurs implémentations incompatibles d'une statcommande qui peuvent le faire. zshest statintégré avec stat -sf "$fd" +modelequel retourne le mode comme une représentation sous forme de chaîne dont le premier caractère représente le type ( ppour pipe). GNU statpeut faire de même avec stat -c %A - <&"$fd", mais doit également stat -c %F - <&"$fd"signaler le type seul. Avec BSD stat: stat -f %St <&"$fd"ou stat -f %HT <&"$fd".

Dire si c'est recherchable

Les applications ne se soucient généralement pas si stdout est un tuyau. Ils peuvent se soucier qu'il soit recherché (bien que généralement ne pas décider de mettre en mémoire tampon ou non).

Pour tester si un fd est recherchable (les tuyaux, les sockets, les périphériques tty ne sont pas recherchés, les fichiers normaux et la plupart des périphériques de bloc le sont généralement), on peut tenter un lseek()appel système relatif avec un décalage de 0 (donc inoffensif). ddest un utilitaire standard qui lseek()sert d' interface, mais il ne peut pas être utilisé pour ce test, car les implémentations n'appelleraient pas lseek()du tout si vous demandez un décalage de 0.

Les zshet ksh93obus ont builtin recherche opérateurs si:

$ strace -e lseek ksh -c ': 1>#((CUR))' | cat
lseek(1, 0, SEEK_CUR)                   = -1 ESPIPE (Illegal seek)
ksh: 1: not seekable
$ strace -e lseek zsh -c 'zmodload zsh/system; sysseek -w current -u 1 0 || syserror'
lseek(1, 0, SEEK_CUR)                   = -1 ESPIPE (Illegal seek)
Illegal seek

Désactiver la mise en mémoire tampon

La scriptcommande utilise une paire pseudo-terminale pour capturer la sortie d'un programme, donc stdout (et stdin et stderr) du programme sera un périphérique pseudo-terminal.

Lorsque la sortie standard est vers un périphérique terminal, il y a encore généralement une mise en mémoire tampon, mais elle est basée sur la ligne. printf/ putset co n'écriront rien tant qu'un caractère de nouvelle ligne ne sera pas sorti. Pour les autres types de fichiers, la mise en mémoire tampon se fait par blocs (de quelques kilo-octets).

Il y a plusieurs options pour désactiver la mise en mémoire tampon qui sont décrits dans un certain nombre de questions et réponses ici (recherche unbuffer ou stdbuf , ne peut pas sortie coupée redirect donne quelques approches) soit en utilisant un pseudo-terminal peut être fait par socat/ script/ expect/ unbuffer(e expectscript) / zsh« s zptyou en injectant du code dans l'exécutable pour désactiver la mise en mémoire tampon comme effectuée par GNU ou de FreeBSD stdbuf.


1
Super réponse, merci beaucoup pour ça!
mowwwalker

Une autre approche, spécifique à Linux, consiste à parcourir le /procrépertoire et, pour chaque /proc/<integer>/répertoire, à rechercher /proc/<integer>/fd/et à trouver un descripteur de fichier ayant le même numéro d'inode dans pipefs serverfault.com/q/48330/363611 Cependant, cela n'est utile dans les scripts que lorsque l'on ne peut pas utiliser les appels système décrits dans la réponse de Stéphane, et est plus une solution de contournement qu'une solution appropriée IMHO
Sergiy Kolodyazhnyy

Sur BSD, lseekréussira sur les terminaux et autres périphériques de caractères, et réinitialisera simplement un compteur qui est augmenté à chaque lecture réussie (). Je ne sais pas si cela les rend "recherchables".
mosvy

@mowwwalker Si cette réponse a résolu votre problème, veuillez prendre un moment et l' accepter en cliquant sur la coche à gauche. Cela marquera la réponse à la question et c'est la façon dont les remerciements sont exprimés sur les sites Stack Exchange.
dessert
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.