Come fa un programma a sapere se stdout è collegato a un terminale o una pipe?


12

Sto riscontrando problemi nel debug di un programma di segfaulting perché l'uscita proprio prima del segfault è ciò di cui ho bisogno, ma questo va perso se sto eseguendo il piping dell'output in un file. Secondo questa risposta: /unix//a/17339/22615 , questo perché il buffer di output del programma si scarica immediatamente quando è collegato a un terminale ma solo in determinati punti quando è collegato a una pipe. Alcune domande qui:

  • In che modo un programma determina a cosa è collegato il suo stdout?

  • In che modo il comando "script" produce lo stesso comportamento di quando il programma scrive su un terminale?

  • Questo può essere ottenuto senza il comando di script?


Una domanda correlata è unix.stackexchange.com/q/513926/5132 .
JdeBP,

Risposte:


23

Dire se un descrittore di file punta a un dispositivo terminale

Un programma può dire se un descrittore di file è associato a un dispositivo tty usando la isatty()funzione C standard (che generalmente sotto fa una ioctl()chiamata di sistema specifica tty innocua che ritornerebbe con un errore quando il fd non punta a un dispositivo tty) .

L' utilità [/ testpuò farlo con il suo -toperatore.

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

Tracciare le chiamate della funzione libc su un sistema GNU / Linux:

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

Traccia delle chiamate di sistema:

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

Dire se indica una pipa

Per determinare se un fd è associato a una pipe / fifo, è possibile utilizzare la fstat()chiamata di sistema , che restituisce una struttura il cui st_modecampo contiene il tipo e le autorizzazioni del file aperto su quel fd. La S_ISFIFO()macro C standard può essere utilizzata su quel st_modecampo per determinare se fd è una pipe / fifo.

Non esiste un'utilità standard in grado di eseguire una fstat(), ma ci sono diverse implementazioni incompatibili di un statcomando che può farlo. zshÈ statincorporato con il stat -sf "$fd" +modequale restituisce la modalità come rappresentazione di stringa il cui primo carattere rappresenta il tipo ( pper pipe). GNU statpuò fare lo stesso con stat -c %A - <&"$fd", ma deve anche stat -c %F - <&"$fd"riportare il tipo da solo. Con BSD stat: stat -f %St <&"$fd"o stat -f %HT <&"$fd".

Dire se è cercabile

In genere, alle applicazioni non importa se stdout è una pipe. A loro potrebbe interessare che sia cercabile (anche se generalmente non decidere se tamponare o meno).

Per verificare se un fd è ricercabile (pipe, socket, dispositivi tty non sono ricercabili, file regolari e la maggior parte dei dispositivi a blocchi lo sono in genere), si può tentare una lseek()chiamata di sistema relativa con un offset di 0 (così innocuo). ddè un'utilità standard che è un'interfaccia per lseek()ma non può essere utilizzata per quel test, poiché le implementazioni non chiamerebbero lseek()affatto se si chiedesse un offset di 0.

Le shell zshe ksh93hanno incorporato gli operatori alla ricerca di:

$ 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

Disabilitare il buffering

Il scriptcomando usa una coppia pseudo-terminale per catturare l'output di un programma, quindi lo stdout del programma (e stdin e stderr) sarà un dispositivo pseudo-terminale.

Quando lo stdout è su un dispositivo terminale, generalmente c'è ancora un po 'di buffering, ma è basato sulla linea. printf/ putse co non scriveranno nulla fino a quando non verrà emesso un carattere di nuova riga. Per altri tipi di file, il buffering è per blocchi (di qualche chilo byte).

Esistono diverse opzioni per disabilitare il buffering che sono discusse in un certo numero di domande e risposte qui (cercare unbuffer o stdbuf , Impossibile reindirizzare l'output di taglio fornisce alcuni approcci) utilizzando uno pseudo-terminale come può essere fatto da socat/ script/ expect/ unbuffer(uno expectscript) / zsh' zptyo iniettando codice nell'eseguibile per disabilitare il buffering come fatto da GNU o FreeBSD stdbuf.


1
Risposta fantastica, grazie mille per questo!
mowwwalker,

Un altro approccio specifico di Linux è di attraversare la /procdirectory e per ogni /proc/<integer>/directory cercare /proc/<integer>/fd/e trovare il descrittore di file con lo stesso numero di inode in pipefs serverfault.com/q/48330/363611 Tuttavia, ciò è utile solo negli script quando non è possibile utilizzare le syscalls descritte nella risposta di Stephane, ed è più una soluzione alternativa che una soluzione adeguata IMHO
Sergiy Kolodyazhnyy,

Su BSD, lseekavrà successo su terminali e altri dispositivi a caratteri e semplicemente re / imposta un contatore che viene aumentato su ogni lettura riuscita (). Non so se questo li rende "ricercabili".
mosvy

@mowwwalker Se questa risposta ha risolto il problema, ti preghiamo di dedicare un momento e accettarlo facendo clic sul segno di spunta a sinistra. Ciò contrassegnerà la domanda come risposta ed è il modo in cui i ringraziamenti sono espressi sui siti di Stack Exchange.
dessert
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.