Wie erkenne ich, ob mein Shell-Skript durch eine Pipe läuft?

252

Wie erkenne ich in einem Shell-Skript, ob seine Standardausgabe an ein Terminal gesendet oder an einen anderen Prozess weitergeleitet wird?

Das Beispiel: Ich möchte Escape-Codes hinzufügen, um die Ausgabe einzufärben, aber nur, wenn sie interaktiv ausgeführt werden, aber nicht, wenn sie weitergeleitet werden, ähnlich wie dies der ls --colorFall ist.

Codeforester
quelle
2
Hier sind einige weitere interessante Testfälle! <a href=" serverfault.com/questions/156470/… für ein Skript, das auf stdin wartet </a>
2
@ user940324 Der richtige Link ist serverfault.com/q/156470/197218
Palec

Antworten:

385

In einer reinen POSIX-Shell

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

Gibt "Terminal" zurück, da die Ausgabe an Ihr Terminal gesendet wird

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

Gibt "kein Terminal" zurück, da die Ausgabe der Klammer an weitergeleitet wird cat.


Die -tFlagge wird in Manpages als beschrieben

-t fd True, wenn der Dateideskriptor fd geöffnet ist und auf ein Terminal verweist.

... wo fdkann eine der üblichen Dateideskriptorzuweisungen sein:

0:     stdin  
1:     stdout  
2:     stderr
dmckee --- Ex-Moderator Kätzchen
quelle
1
@ Kelvin Das dortige Manpage-Snippet schlägt dies vor, aber diese Dateideskriptoren werden nicht standardmäßig zugewiesen.
dmckee --- Ex-Moderator Kätzchen
41
Zur Verdeutlichung wird das -tFlag in POSIX angegeben und sollte daher für jede POSIX-kompatible Shell funktionieren (dh es ist keine Bash-Erweiterung). pubs.opengroup.org/onlinepubs/009695399/utilities/test.html
FireFly
Funktioniert auch, wenn ein Skript als ssh-Fernbefehl ausgeführt wird. Beste Antwort aller Zeiten und sehr einfach.
linux_newbie
Ich bin damit einverstanden, dass die Antwort nach Ihrer Bearbeitung (Revision 5) klarer als in Revision 3 und auch sachlich korrekt ist (wenn Sie ignorieren, dass "Retouren" sehr informell verwendet werden, wenn "Drucke" präziser wären).
Palec
War auf der Suche nach einer fishShell-Antwort. Die Verwendung testist ordentlich, aber ich kann das Beispiel in Klammern nicht ausprobieren, da dies nicht unterstützt wird. Ich habe versucht, es analog zu verpacken begin; ...; end, aber das schien nicht zu funktionieren, und habe den positiven Codeblock einfach erneut ausgeführt. Ich dachte, ich müsste es vielleicht benutzen, statusaber das scheint nicht nach Rohrleitungen zu suchen. Ich denke, ich möchte im Wesentlichen überprüfen, ob STDOUT eines vorhergehenden Befehls / Skripts dank dieser klarstellenden Antworten nicht auf das Terminal eingestellt ist.
Pysis
126

Es gibt keine narrensichere Methode, um festzustellen, ob STDIN, STDOUT oder STDERR zu / von Ihrem Skript geleitet werden, hauptsächlich aufgrund von Programmen wie ssh.

Dinge, die "normal" funktionieren

Die folgende Bash-Lösung funktioniert beispielsweise in einer interaktiven Shell ordnungsgemäß:

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

Aber sie funktionieren nicht immer

Wenn Sie diesen Befehl jedoch als Nicht-TTY- sshBefehl ausführen , sehen STD-Streams immer so aus, als würden sie weitergeleitet. Um dies zu demonstrieren, verwenden Sie STDIN, weil es einfacher ist:

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

Warum es wichtig ist

Dies ist eine ziemlich große Sache, da es impliziert, dass ein Bash-Skript nicht erkennen kann, ob ein Nicht-Tty- sshBefehl weitergeleitet wird oder nicht. Beachten Sie, dass dieses unglückliche Verhalten eingeführt wurde, als neuere Versionen von sshPipes für Nicht-TTY-STDIO verwendet wurden. In früheren Versionen wurden Sockets verwendet, die durch Verwendung von Bash innerhalb von Bash unterschieden werden KÖNNTEN [[ -S ]].

Wenn es darauf ankommt

Diese Einschränkung verursacht normalerweise Probleme, wenn Sie ein Bash-Skript schreiben möchten, dessen Verhalten einem kompilierten Dienstprogramm ähnelt, z cat. Zum Beispiel catermöglicht es die folgenden flexible Verhalten gleichzeitig verschiedene Eingangsquellen in der Handhabung und ist intelligent genug , um zu bestimmen , ob es verrohrt Eingang unabhängig davon empfängt , ob nicht-TTY TTY oder Zwang sshverwendet wird:

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

Sie können so etwas nur tun, wenn Sie zuverlässig feststellen können, ob Rohre betroffen sind oder nicht. Andernfalls führt das Ausführen eines Befehls, der STDIN liest, wenn keine Eingabe von Pipes oder Umleitungen verfügbar ist, dazu, dass das Skript hängt und auf die STDIN-Eingabe wartet.

Andere Dinge, die nicht funktionieren

Bei dem Versuch, dieses Problem zu lösen, habe ich mir verschiedene Techniken angesehen, die das Problem nicht lösen können, einschließlich solcher, die Folgendes umfassen:

  • Untersuchen von SSH-Umgebungsvariablen
  • Verwenden statvon Dateideskriptoren für / dev / stdin
  • Untersuchen des interaktiven Modus über [[ "${-}" =~ 'i' ]]
  • Prüfung des Status über ttyundtty -s
  • sshStatus prüfen über[[ "$(ps -o comm= -p $PPID)" =~ 'sshd' ]]

Beachten Sie, dass /procSie möglicherweise Glück haben , wenn Sie ein Betriebssystem verwenden, das das virtuelle Dateisystem unterstützt, indem Sie den symbolischen Links für STDIO folgen, um festzustellen, ob eine Pipe verwendet wird oder nicht. Es handelt sich jedoch /procnicht um eine plattformübergreifende POSIX-kompatible Lösung.

Ich bin äußerst interessant bei der Lösung dieses Problems. Lassen Sie mich wissen, wenn Sie an eine andere Technik denken, die möglicherweise funktioniert, vorzugsweise POSIX-basierte Lösungen, die sowohl unter Linux als auch unter BSD funktionieren.

Dejay Clayton
quelle
2
Die eindeutige Überprüfung von Umgebungsvariablen oder Prozessnamen ist eine sehr unzuverlässige Heuristik. Aber können Sie etwas näher erläutern, warum die anderen Heuristiken für diesen Zweck nicht geeignet sind oder wo ihr Problem liegt? Zum Beispiel sehe ich keinen Unterschied in der Ausgabe eines statAufrufs von / dev / stdin. Und warum funktioniert "${-}"oder tty -snicht? Ich habe mir auch den Quellcode von angesehen, cataber nicht gesehen, welcher Teil dort die Magie ausübt, die Sie in der POSIX-Shell nicht ausführen können. Könntest du das weiter erläutern?
Josch
30

Der Befehl test(integriert bash) bietet eine Option zum Überprüfen, ob ein Dateideskriptor ein tty ist.

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

Siehe " man test" oder " man bash" und suchen Sie nach " -t"

Beano
quelle
3
+1 für "man test", weil / usr / bin / test auch in einer Shell funktioniert, die -t in ihrem eingebauten Test nicht implementiert
Neil Mayhew
4
Wie FireFly in der Antwort von dmckee feststellte, entspricht eine Shell, die -t nicht implementiert, nicht POSIX.
scy
Siehe auch bash's integriert help test(und help helpfür mehr), dann info bashfür detailliertere Informationen. Diese Befehle eignen sich hervorragend, wenn Sie jemals offline Skripte erstellen oder einfach nur ein umfassenderes Verständnis erlangen möchten.
Joel Purra
13

Sie erwähnen nicht, welche Shell Sie verwenden, aber in Bash können Sie dies tun:

#!/bin/bash

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

Unter Solaris funktioniert der Vorschlag von Dejay Clayton hauptsächlich. Das -p reagiert nicht wie gewünscht.

bash_redir_test.sh sieht aus wie:

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

Unter Linux funktioniert es hervorragend:

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

Unter 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
quelle
Interessanterweise wünschte ich mir, ich hätte Zugriff auf Solaris zum Testen. Wenn Ihre Solaris-Instanz das Dateisystem "/ proc" verwendet, gibt es zuverlässigere Lösungen, bei denen nach symbolischen Links "/ proc" für stdin, stdout und stderr gesucht wird.
Dejay Clayton
1

Der folgende Code (nur in Linux Bash 4.4 getestet) sollte weder als portabel angesehen noch empfohlen werden. Der Vollständigkeit halber ist er jedoch hier:

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

Ich weiß nicht warum, aber es scheint, dass der Dateideskriptor "3" irgendwie erstellt wird, wenn eine Bash-Funktion STDIN-Piping hat.

Ich hoffe es hilft,

ATorras
quelle