Como detectar se meu script de shell está executando através de um pipe?

252

Como detectar de dentro de um script de shell se sua saída padrão está sendo enviada para um terminal ou se é canalizada para outro processo?

O caso em questão: eu gostaria de adicionar códigos de escape para colorir a saída, mas apenas quando executados de forma interativa, mas não quando canalizados, semelhante ao que ls --colorfaz.

codeforester
fonte
2
Aqui estão alguns casos de teste mais interessantes! <a href=" serverfault.com/questions/156470/… para um script que está aguardando stdin</a>
2
@ user940324 O link correto é serverfault.com/q/156470/197218
Palec

Respostas:

385

Em um shell POSIX puro,

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

retorna "terminal", porque a saída é enviada ao seu terminal, enquanto

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

retorna "não um terminal", porque a saída do parêntese é canalizada para cat.


O -tsinalizador é descrito nas páginas do manual como

-t fd True se o descritor de arquivo fd estiver aberto e se referir a um terminal.

... onde fdpode ser uma das atribuições usuais de descritor de arquivo:

0:     stdin  
1:     stdout  
2:     stderr
dmckee --- gatinho ex-moderador
fonte
1
@ Kelvin O trecho da página de manual sugere que sim, mas esses descritores de arquivo não são atribuídos por padrão.
dmckee --- gatinho ex-moderador 14/05
41
Para esclarecer, o -tsinalizador é especificado no POSIX e, portanto, deve funcionar para qualquer shell compatível com o POSIX (ou seja, não é uma extensão do bash). pubs.opengroup.org/onlinepubs/009695399/utilities/test.html
FireFly
Funciona ao executar um script como comando remoto ssh também. Melhor resposta de sempre e muito simples.
Linux_newbie
Concordo que após a sua edição (revisão 5), a resposta é mais clara do que na revisão 3 e também factualmente correta (ignorando que "retornos" são usados ​​de maneira muito informal, onde "impressões" seria mais preciso).
Palec
Estava procurando uma fishresposta concha. O uso testé legal, mas não posso tentar o exemplo entre parênteses, pois isso não é suportado. Tentei agrupá-lo de forma análoga begin; ...; end, mas isso não pareceu funcionar e apenas executei o bloco de código positivo novamente. Achei que talvez statusfosse necessário usar, mas isso não parece verificar a tubulação. Acho que quero essencialmente verificar se o STDOUT de um comando / script anterior não está definido para o terminal, graças a essas respostas esclarecedoras.
Pysis
126

Não há uma maneira infalível de determinar se STDIN, STDOUT ou STDERR estão sendo canalizados para / do seu script, principalmente por causa de programas como o ssh.

Coisas que "normalmente" funcionam

Por exemplo, a seguinte solução do bash funciona corretamente em um shell interativo:

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

Mas eles nem sempre funcionam

No entanto, ao executar este comando como um comando não-TTY ssh, os fluxos STD sempre parecem estar sendo canalizados. Para demonstrar isso, use STDIN porque é mais fácil:

# 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 ${?}'

Por que isso importa

Isso é muito importante, porque implica que não há como um script bash dizer se um sshcomando não-tty está sendo canalizado ou não. Observe que esse comportamento infeliz foi introduzido quando versões recentes de sshcomeçaram a usar pipes para STDIO não-TTY. As versões anteriores usavam soquetes, que PODEM ser diferenciados de dentro do bash usando [[ -S ]].

Quando importa

Essa limitação normalmente causa problemas quando você deseja escrever um script bash com comportamento semelhante a um utilitário compilado, como cat. Por exemplo, catpermite o seguinte comportamento flexível ao lidar com várias fontes de entrada simultaneamente e é inteligente o suficiente para determinar se está recebendo entrada canalizada, independentemente de não sshestar sendo usado TTY ou forçado :

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

Você só pode fazer algo assim se puder determinar com segurança se os tubos estão envolvidos ou não. Caso contrário, a execução de um comando que lê STDIN quando nenhuma entrada está disponível nos pipes ou redirecionamentos resultará na interrupção do script e na espera pela entrada STDIN.

Outras coisas que não funcionam

Ao tentar resolver esse problema, observei várias técnicas que não conseguem resolver o problema, incluindo aquelas que envolvem:

  • examinando variáveis ​​de ambiente SSH
  • usando statdescritores de arquivo / dev / stdin
  • examinando o modo interativo via [[ "${-}" =~ 'i' ]]
  • examinando o status tty via ttyetty -s
  • examinando o sshstatus via[[ "$(ps -o comm= -p $PPID)" =~ 'sshd' ]]

Observe que, se você estiver usando um sistema operacional compatível com o /procsistema de arquivos virtual, poderá ter sorte seguindo os links simbólicos do STDIO para determinar se um pipe está sendo usado ou não. No entanto, /procnão é uma solução compatível com POSIX multiplataforma.

Eu sou extremamente interessante na solução desse problema, então, deixe-me saber se você pensa em alguma outra técnica que possa funcionar, de preferência soluções baseadas em POSIX que funcionem no Linux e no BSD.

Dejay Clayton
fonte
2
A inspeção clara de variáveis ​​de ambiente ou nomes de processos é uma heurística não confiável. Mas você poderia expandir um pouco por que as outras heurísticas não são adequadas para esse propósito ou qual é o problema delas? Por exemplo, não vejo diferença na saída de uma statchamada em / dev / stdin. E por que funciona "${-}"ou tty -snão? Eu também procurei no código fonte, catmas não consigo ver qual parte está fazendo a mágica que você não pode fazer no shell POSIX. Você poderia expandir isso?
Josch
30

O comando test(embutido bash), tem uma opção para verificar se um descritor de arquivo é um tty.

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

Veja " man test" ou " man bash" e procure por " -t"

Beano
fonte
3
+1 para "teste de homem", porque / usr / bin / teste vai funcionar mesmo em um shell que não implementa -t em sua build-in test
Neil Mayhew
4
Como observado pelo FireFly na resposta do dmckee, um shell que não implementa -t não está em conformidade com o POSIX.
Scy
Veja também o built-in do bash help test(e help helppara mais) e, em seguida, info bashpara informações mais detalhadas. Esses comandos são ótimos se você acabar criando scripts offline ou quiser apenas obter um entendimento mais amplo.
Joel Purra
13

Você não menciona qual shell está usando, mas no Bash, você pode fazer isso:

#!/bin/bash

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

No Solaris, a sugestão de Dejay Clayton funciona principalmente. O -p não responde conforme desejado.

bash_redir_test.sh se parece com:

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

No Linux, funciona muito bem:

:$ ./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

No 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

:# 
sbj3
fonte
Interessante, eu gostaria de ter acesso ao Solaris para testar. Se sua instância do Solaris usa o sistema de arquivos "/ proc", existem soluções mais confiáveis ​​que envolvem a pesquisa de links simbólicos "/ proc" para stdin, stdout e stderr.
DeJay Clayton
1

O código a seguir (testado apenas no linux bash 4.4) não deve ser considerado portátil nem recomendado , mas por uma questão de integridade, aqui está:

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

Não sei por que, mas parece que o descritor de arquivo "3" é criado de alguma forma quando uma função bash tem STDIN canalizado.

Espero que ajude,

ATorras
fonte