Come rilevare se il mio script shell è in esecuzione attraverso una pipe?


252

Come posso rilevare dall'interno di uno script shell se il suo output standard viene inviato a un terminale o se viene reindirizzato a un altro processo?

Il caso in questione: vorrei aggiungere codici di escape per colorare l'output, ma solo quando eseguito in modo interattivo, ma non quando viene convogliato, in modo simile a ciò che ls --colorfa.


2
Ecco alcuni casi di test più interessanti! <a href=" serverfault.com/questions/156470/… per uno script in attesa su stdin</a>

2
@ user940324 Il link corretto è serverfault.com/q/156470/197218
Palec

Risposte:


385

In una pura shell POSIX,

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

restituisce "terminale", poiché l'output viene inviato al terminale, mentre

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

restituisce "non un terminale", poiché viene convogliato l'output della parentesi cat.


La -tbandiera è descritta nelle pagine man come

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

... dove fdpuò essere una delle solite assegnazioni di descrittori di file:

0:     stdin  
1:     stdout  
2:     stderr

1
@Kelvin Lo snippet della pagina man suggerisce che dovrebbe, ma quei descrittori di file non sono assegnati di default.
dmckee --- ex gattino moderatore

41
Per chiarire, il -tflag è specificato in POSIX e quindi dovrebbe funzionare per qualsiasi shell compatibile con POSIX (ovvero non è un'estensione bash). pubs.opengroup.org/onlinepubs/009695399/utilities/test.html
FireFly

Funziona anche quando si esegue uno script come comando remoto ssh. La migliore risposta di sempre e molto semplice.
linux_newbie il

Concordo sul fatto che dopo la tua modifica (revisione 5), la risposta è più chiara che nella revisione 3 e anche di fatto corretta (ignorando che "ritorni" viene utilizzato in modo molto informale dove "stampe" sarebbe più preciso).
Palec,

Stavo cercando una fishrisposta shell. L'uso testè pulito, ma non posso provare l'esempio tra parentesi poiché non è supportato. Ho provato a racchiuderlo in modo analogo begin; ...; end, ma non sembrava funzionare e ha eseguito nuovamente il blocco di codice positivo. Ho pensato che potrei aver bisogno di usare, statusma questo non sembra verificare la presenza di tubazioni. Immagino che essenzialmente voglio verificare se STDOUT di un comando / script precedente non è impostato sul terminale, grazie a queste risposte chiarenti.
Pysis,

126

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

Cose che "normalmente" funzionano

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

[[ -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 funzionano sempre

Tuttavia, quando si esegue questo comando come comando non TTY ssh, i flussi STD sembrano sempre essere in fase di piping. Per dimostrarlo, utilizzare 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 c'è modo per uno script bash di dire se un sshcomando non tty viene inviato o meno. Si noti che questo sfortunato comportamento è stato introdotto quando le versioni recenti di hanno sshiniziato a utilizzare pipe per STDIO non TTY. Le versioni precedenti utilizzavano socket, che POTREBBE essere differenziati all'interno di bash utilizzando [[ -S ]].

Quando conta

Questa limitazione normalmente causa problemi quando si desidera scrivere uno script bash con un comportamento simile a un'utilità compilata, ad esempio cat. Ad esempio, catconsente il seguente comportamento flessibile nella gestione simultanea di varie origini di input ed è abbastanza intelligente da determinare se sta ricevendo input di pipeline indipendentemente dal fatto che sshvenga utilizzato 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 riesci a determinare in modo affidabile se i tubi sono coinvolti o meno. Altrimenti, l'esecuzione di un comando che legge STDIN quando non è disponibile alcun input da entrambe le pipe o il reindirizzamento comporterà la sospensione dello script e l'attesa dell'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 coinvolgono:

  • esaminando le variabili di ambiente SSH
  • usando stati descrittori di file su / dev / stdin
  • esaminando la modalità interattiva tramite [[ "${-}" =~ 'i' ]]
  • esaminando lo stato tty tramite ttyetty -s
  • esame dello sshstato tramite[[ "$(ps -o comm= -p $PPID)" =~ 'sshd' ]]

Si noti che se si utilizza un sistema operativo che supporta il /procfilesystem virtuale, si potrebbe avere fortuna seguendo i collegamenti simbolici per STDIO per determinare se una pipe viene utilizzata o meno. Tuttavia, /procnon è una soluzione multipiattaforma compatibile con POSIX.

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


2
L'ispezione chiara delle variabili di ambiente o dei nomi dei processi sono euristiche molto inaffidabili. Ma potresti ampliare un po 'perché le altre euristiche non sono adatte a questo scopo o qual è il loro problema? Ad esempio, non vedo alcuna differenza nell'output di una statchiamata su / dev / stdin. E perché funziona "${-}"o tty -sno? Ho anche esaminato il codice sorgente di catma non riesco a vedere quale parte sta facendo la magia lì che non puoi fare nella shell POSIX. Potrebbe approfondire che?
Jos

30

Il comando test(integrato 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"


3
+1 per "man test" perché / usr / bin / test funzionerà anche in una shell che non implementa -t nel suo test
integrato

4
Come notato da FireFly nella risposta di dmckee, una shell che non implementa -t non è conforme a POSIX.
scy

Vedi anche builtin di bash help test(e help helpper altro), quindi info bashper informazioni più approfondite. Questi comandi sono fantastici se finisci per scrivere script offline o vuoi semplicemente avere una comprensione più ampia.
Joel Purra

13

Non dici quale shell stai usando, ma in Bash puoi farlo:

#!/bin/bash

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

6

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

bash_redir_test.sh assomiglia a:

[[ -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

:# 

Interessante, vorrei avere accesso a Solaris per testare. Se l'istanza di Solaris utilizza il filesystem "/ proc", esistono soluzioni più affidabili che prevedono la ricerca di collegamenti simbolici "/ proc" per stdin, stdout e stderr.
Dejay Clayton,

1

Il seguente codice (testato solo in Linux Bash 4.4) non deve essere considerato portatile né raccomandato , ma per completezza qui è:

ls /proc/$$/fdinfo/* >/dev/null 2>&1 || grep -q 'flags: 00$' /proc/$$/fdinfo/0 && echo "pipe detected"

Non so perché, ma sembra che il descrittore di file "3" sia in qualche modo creato quando una funzione bash ha il piping STDIN.

Spero che sia d'aiuto,

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.