Come rilevare se il mio script di shell sta scorrendo attraverso una pipe?

Come posso rilevare da uno script di shell se il suo output standard viene inviato a un terminale o se è collegato a un altro processo?

Il caso in questione: mi piacerebbe aggiungere codici di escape per colorare l’output, ma solo quando si esegue in modo interattivo, ma non quando si esegue il piping, simile a quello che fa ls --color .

In una shell POSIX pura,

 if [ -t 1 ] ; then echo terminal; else echo "not a terminal"; fi 

restituisce “terminale”, perché l’output viene inviato al terminale, mentre

 (if [ -t 1 ] ; then echo terminal; else echo "not a terminal"; fi) | cat 

restituisce “non un terminale”, perché l’output del parenthetic viene convogliato su cat .


Il flag -t è descritto nelle pagine man come

-t fd True se il descrittore di file fd è aperto e fa riferimento a un terminale.

… dove fd può essere uno dei soliti compiti del descrittore di file:

 0: stdin 1: stdout 2: stderr 

Non esiste un metodo infallibile per determinare se STDIN, STDOUT o STDERR vengono reindirizzati al / dal tuo script, principalmente a causa di programmi come ssh .

Cose che “normalmente” funzionano

Ad esempio, la seguente soluzione di bash funziona correttamente in una shell intertriggers:

 [[ -t 1 ]] && \ echo 'STDOUT is attached to TTY' [[ -p /dev/stdout ]] && \ echo 'STDOUT is attached to a pipe' [[ ! -t 1 && ! -p /dev/stdout ]] && \ echo 'STDOUT is attached to a redirection' 

Ma non sempre funzionano

Tuttavia, quando si esegue questo comando come comando ssh non TTY, i flussi STD hanno sempre l’ aspetto di essere inviati in pipe. Per dimostrarlo, usando STDIN perché è più semplice:

 # CORRECT: Forced-tty mode correctly reports '1', which represents # no pipe. ssh -t localhost '[[ -p /dev/stdin ]]; echo ${?}' # CORRECT: Issuing a piped command in forced-tty mode correctly # reports '0', which represents a pipe. ssh -t localhost 'echo hi | [[ -p /dev/stdin ]]; echo ${?}' # INCORRECT: Non-tty mode reports '0', which represents a pipe, # even though one isn't specified here. ssh -T localhost '[[ -p /dev/stdin ]]; echo ${?}' 

Perchè importa

Questo è un grosso problema, perché implica che non vi è alcun modo per uno script bash per stabilire se un comando sty non tty viene sottoposto a pipe o meno. Si noti che questo sfortunato comportamento è stato introdotto quando le versioni recenti di ssh iniziato a utilizzare pipe per STDIO non TTY. Le versioni precedenti utilizzavano socket, che potevano essere differenziati dall’interno di bash usando [[ -S ]] .

Quando conta

Questa limitazione normalmente causa problemi quando si desidera scrivere uno script bash con un comportamento simile a un’utilità compilata, come cat . Ad esempio, cat consente il seguente comportamento flessibile nella gestione di varie origini di input contemporaneamente ed è sufficientemente intelligente per determinare se sta ricevendo l’input in pipe indipendentemente dal fatto che venga utilizzato un sty non TTY o forzato-TTY:

 ssh -t localhost 'echo piped | cat - < ( echo substituted )' ssh -T localhost 'echo piped | cat - <( echo substituted )' 

Puoi fare qualcosa del genere solo se puoi determinare in modo affidabile se le pipe sono coinvolte o meno. Altrimenti, eseguendo un comando che legge STDIN quando nessun input è disponibile da nessuno dei tubi o dal reindirizzamento, lo script verrà sospeso e in attesa di input STDIN.

Altre cose che non funzionano

Nel tentativo di risolvere questo problema, ho esaminato diverse tecniche che non riescono a risolvere il problema, comprese quelle che implicano:

  • esaminare le variabili di ambiente SSH
  • usando stat sui descrittori di file / dev / stdin
  • esaminando la modalità intertriggers tramite [[ "${-}" =~ 'i' ]]
  • esaminare lo stato di tty tramite tty e tty -s
  • esaminare lo stato ssh tramite [[ "$(ps -o comm= -p $PPID)" =~ 'sshd' ]]

Notare che se si utilizza un sistema operativo che supporta il /proc virtuale /proc , si potrebbe avere la fortuna di seguire i collegamenti simbolici di STDIO per determinare se un pipe viene usato o meno. Tuttavia, /proc non è una soluzione multipiattaforma, compatibile con POSIX.

Sono estremamente interessante nel risolvere questo problema, quindi fatemi sapere se pensate a qualsiasi altra tecnica che potrebbe funzionare, preferibilmente soluzioni basate su POSIX che funzionano su Linux e BSD.

Il test comando (incorporato in bash ), ha un’opzione per verificare se un descrittore di file è un tty.

 if [ -t 1 ]; then # stdout is a tty fi 

Vedi ” man test ” o ” man bash ” e cerca ” -t

Non menzioni la shell che stai usando, ma in Bash puoi farlo:

 #!/bin/bash if [[ -t 1 ]]; then # stdout is a terminal else # stdout is not a terminal fi 

Su Solaris, il suggerimento di Dejay Clayton funziona principalmente. Il -p non risponde come desiderato.

bash_redir_test.sh ha il seguente aspetto:

 [[ -t 1 ]] && \ echo 'STDOUT is attached to TTY' [[ -p /dev/stdout ]] && \ echo 'STDOUT is attached to a pipe' [[ ! -t 1 && ! -p /dev/stdout ]] && \ echo 'STDOUT is attached to a redirection' 

Su Linux, funziona alla grande:

 :$ ./bash_redir_test.sh STDOUT is attached to TTY :$ ./bash_redir_test.sh | xargs echo STDOUT is attached to a pipe :$ rm bash_redir_test.log :$ ./bash_redir_test.sh >> bash_redir_test.log :$ tail bash_redir_test.log STDOUT is attached to a redirection 

Su Solaris:

 :# ./bash_redir_test.sh STDOUT is attached to TTY :# ./bash_redir_test.sh | xargs echo STDOUT is attached to a redirection :# rm bash_redir_test.log bash_redir_test.log: No such file or directory :# ./bash_redir_test.sh >> bash_redir_test.log :# tail bash_redir_test.log STDOUT is attached to a redirection :#