Comment détecter si mon script shell s'exécute via un canal?

252

Comment détecter à partir d'un script shell si sa sortie standard est envoyée à un terminal ou si elle est dirigée vers un autre processus?

Le cas d'espèce: je voudrais ajouter des codes d'échappement pour coloriser la sortie, mais uniquement lorsqu'il est exécuté de manière interactive, mais pas lorsqu'il est canalisé, comme c'est le ls --colorcas.

codeforester
la source
2
Voici quelques cas de test plus intéressants! <a href=" serverfault.com/questions/156470/… pour un script qui attend sur stdin</a>
2
@ user940324 Le lien correct est serverfault.com/q/156470/197218
Palec

Réponses:

385

Dans un shell POSIX pur,

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

renvoie "terminal", car la sortie est envoyée à votre terminal, alors que

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

renvoie "pas un terminal", car la sortie de la parenthèse est dirigée vers cat.


Le -tdrapeau est décrit dans les pages de manuel comme

-t fd Vrai si le descripteur de fichier fd est ouvert et fait référence à un terminal.

... où fdpeut être l'une des affectations de descripteurs de fichiers habituelles:

0:     stdin  
1:     stdout  
2:     stderr
dmckee --- chaton ex-modérateur
la source
1
@Kelvin L'extrait de page de manuel suggère qu'il devrait l'être, mais ces descripteurs de fichiers ne sont pas attribués par défaut.
dmckee --- chaton ex-modérateur
41
Pour clarifier, l' -tindicateur est spécifié dans POSIX et devrait donc fonctionner pour tout shell compatible POSIX (c'est-à-dire qu'il ne s'agit pas d'une extension bash). pubs.opengroup.org/onlinepubs/009695399/utilities/test.html
FireFly
Fonctionne également lors de l'exécution d'un script en tant que commande distante ssh. Meilleure réponse jamais et très simple.
linux_newbie
Je suis d'accord qu'après votre édition (révision 5), la réponse est plus claire que dans la révision 3 et également correcte dans les faits (en ignorant que «retours» est utilisé de manière très informelle où «impressions» serait plus précis).
Palec
Je cherchais une fishréponse shell. L'utilisation testest soignée, mais je ne peux pas essayer l'exemple entre parenthèses car cela n'est pas pris en charge. J'ai essayé de l'envelopper dans un analogue begin; ...; end, mais cela ne semblait pas fonctionner, et a simplement exécuté à nouveau le bloc de code positif. J'ai pensé que je pourrais avoir besoin d'utiliser, statusmais cela ne semble pas vérifier la tuyauterie. Je suppose que je veux essentiellement vérifier si STDOUT d'une commande / script précédente n'est pas définie sur le terminal, grâce à ces réponses de clarification.
Pysis
126

Il n'existe aucun moyen infaillible de déterminer si STDIN, STDOUT ou STDERR sont dirigés vers / depuis votre script, principalement à cause de programmes comme ssh.

Choses qui fonctionnent «normalement»

Par exemple, la solution bash suivante fonctionne correctement dans un shell interactif:

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

Mais ils ne fonctionnent pas toujours

Cependant, lors de l'exécution de cette commande en tant que commande non TTY ssh, les flux STD semblent toujours être acheminés. Pour le démontrer, utilisez STDIN car c'est plus simple:

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

Pourquoi est-ce important

C'est un gros problème, car cela implique qu'il n'y a aucun moyen pour un script bash de dire si une sshcommande non tty est envoyée ou non. Notez que ce comportement malheureux a été introduit lorsque des versions récentes de ont sshcommencé à utiliser des canaux pour les non-TTY STDIO. Les versions précédentes utilisaient des sockets, qui POURRAIENT être différenciés de bash en utilisant [[ -S ]].

Quand c'est important

Cette limitation pose normalement des problèmes lorsque vous souhaitez écrire un script bash qui a un comportement similaire à un utilitaire compilé, tel que cat. Par exemple, catpermet le comportement flexible suivant dans la gestion simultanée de diverses sources d'entrée et est suffisamment intelligent pour déterminer s'il reçoit une entrée canalisée, que l' sshon utilise ou non un ATS forcé :

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

Vous ne pouvez faire quelque chose comme ça que si vous pouvez déterminer de manière fiable si des tuyaux sont impliqués ou non. Sinon, l'exécution d'une commande qui lit STDIN lorsqu'aucune entrée n'est disponible à partir des canaux ou de la redirection entraîne le blocage du script et l'attente de l'entrée STDIN.

D'autres choses qui ne fonctionnent pas

En essayant de résoudre ce problème, j'ai examiné plusieurs techniques qui ne parviennent pas à résoudre le problème, y compris celles qui impliquent:

  • examen des variables d'environnement SSH
  • utilisation statde descripteurs de fichiers on / dev / stdin
  • examen du mode interactif via [[ "${-}" =~ 'i' ]]
  • examen du statut tty via ttyettty -s
  • examen du sshstatut via[[ "$(ps -o comm= -p $PPID)" =~ 'sshd' ]]

Notez que si vous utilisez un système d'exploitation qui prend en charge le /procsystème de fichiers virtuel, vous aurez peut-être de la chance en suivant les liens symboliques de STDIO pour déterminer si un canal est utilisé ou non. Cependant, il /procne s'agit pas d'une solution multiplateforme compatible POSIX.

Je suis extrêmement intéressé à résoudre ce problème, alors faites-moi savoir si vous pensez à une autre technique qui pourrait fonctionner, de préférence des solutions basées sur POSIX qui fonctionnent à la fois sur Linux et BSD.

Dejay Clayton
la source
2
L'inspection claire des variables d'environnement ou des noms de processus est une heuristique très peu fiable. Mais pourriez-vous expliquer un peu pourquoi les autres heuristiques ne conviennent pas à cet usage ou quel est leur problème? Par exemple, je ne vois aucune différence dans la sortie d'un statappel sur / dev / stdin. Et pourquoi ça marche "${-}"ou tty -spas? J'ai également examiné le code source de catmais je ne vois pas quelle partie fait la magie que vous ne pouvez pas faire dans le shell POSIX. Pourriez-vous développer ceci?
josch
30

La commande test(intégrée bash) a une option pour vérifier si un descripteur de fichier est un tty.

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

Voir " man test" ou " man bash" et rechercher " -t"

Beano
la source
3
+1 pour "man test" car / usr / bin / test fonctionnera même dans un shell qui n'implémente pas -t dans son test de build
Neil Mayhew
4
Comme indiqué par FireFly dans la réponse de dmckee, un shell qui n'implémente pas -t n'est pas conforme à POSIX.
scy
Voir aussi la commande intégrée de bash help test(et help helppour plus), puis info bashpour des informations plus détaillées. Ces commandes sont excellentes si vous finissez par créer des scripts hors ligne ou si vous souhaitez simplement obtenir une compréhension plus large.
Joel Purra
13

Vous ne mentionnez pas le shell que vous utilisez, mais dans Bash, vous pouvez le faire:

#!/bin/bash

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

Sur Solaris, la suggestion de Dejay Clayton fonctionne principalement. -P ne répond pas comme souhaité.

bash_redir_test.sh ressemble à:

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

Sous Linux, cela fonctionne très bien:

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

Sur 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
la source
Intéressant, j'aurais aimé avoir accès à Solaris pour tester. Si votre instance Solaris utilise le système de fichiers "/ proc", il existe des solutions plus fiables qui impliquent la recherche de liens symboliques "/ proc" pour stdin, stdout et stderr.
Dejay Clayton
1

Le code suivant (testé uniquement dans linux bash 4.4) ne doit pas être considéré comme portable ni recommandé , mais pour des raisons d'exhaustivité, il est ici:

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

Je ne sais pas pourquoi, mais il semble que le descripteur de fichier "3" soit en quelque sorte créé quand une fonction bash a canalisé STDIN.

J'espère que ça aide,

ATorras
la source