it-swarm.com.de

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

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

Ein typischer Fall: Ich möchte Escape-Codes hinzufügen, um die Ausgabe zu kolorieren, aber nur, wenn sie interaktiv ausgeführt werden, aber nicht, wenn sie weitergeleitet werden, ähnlich wie ls --color.

208
user111422

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 des Parenthetikums an cat geleitet wird.


Das Flag -t wird in den Manpages als beschrieben

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

... wobei fd eine der üblichen Dateideskriptorzuweisungen sein kann:

0:     stdin  
1:     stdout  
2:     stderr
314
dmckee

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

Dinge, die "normalerweise" funktionieren

Beispielsweise funktioniert die folgende Bash-Lösung 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 einen Nicht-TTY ssh-Befehl ausführen, sehen STD-Streams always 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 dies impliziert, dass es für ein Bash-Skript nicht möglich ist, festzustellen, ob ein Nicht-tty-Befehl ssh weitergeleitet wird oder nicht. Beachten Sie, dass dieses unglückliche Verhalten eingeführt wurde, als in den letzten Versionen von ssh Pipes für Nicht-TTY STDIO verwendet wurden. In früheren Versionen wurden Sockets verwendet, die innerhalb von bash mit [[ -S ]] unterschieden werden konnten.

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. B. cat. Beispielsweise ermöglicht cat das folgende flexible Verhalten beim gleichzeitigen Umgang mit verschiedenen Eingabequellen und ist intelligent genug, um zu bestimmen, ob Pipe-Eingaben empfangen werden, unabhängig davon, ob nicht-TTY oder erzwungene TTY ssh verwendet 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 beteiligt sind oder nicht. Andernfalls führt die Ausführung eines Befehls, der STDIN liest, wenn keine Eingabe von einer der Pipes oder der Umleitung 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, darunter auch folgende:

  • sSH-Umgebungsvariablen untersuchen
  • verwenden von stat bei/dev/stdin-Dateideskriptoren
  • untersuchen des interaktiven Modus über [[ "${-}" =~ 'i' ]]
  • tty-Status über tty und tty -s prüfen
  • ssh status über [[ "$(ps -o comm= -p $PPID)" =~ 'sshd' ]] prüfen

Wenn Sie ein Betriebssystem verwenden, das das virtuelle Dateisystem /proc unterstützt, können Sie Glück haben, wenn Sie den symbolischen Links für STDIO folgen, um festzustellen, ob eine Pipe verwendet wird oder nicht. /proc ist jedoch keine plattformübergreifende, POSIX-kompatible Lösung.

Ich bin äußerst daran interessiert, dieses Problem zu lösen. Bitte lassen Sie mich wissen, wenn Sie an eine andere Technik denken, am besten POSIX-basierte Lösungen, die sowohl unter Linux als auch unter BSD funktionieren.

95
Dejay Clayton

Mit dem Befehl test (eingebaut in bash) kann geprüft werden, 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".

27
Beano

Sie erwähnen nicht, welche Shell Sie verwenden. In Bash können Sie Folgendes tun:

#!/bin/bash

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

Unter Solaris funktioniert der Vorschlag von Dejay Clayton meistens. Das -p antwortet nicht wie gewünscht. 

bash_redir_test.sh sieht so aus:

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

:# 
4
sbj3

Der folgende Code (nur in Linux Bash 4.4 getestet) sollte nicht als tragbar oder empfohlen betrachtet werden , der Vollständigkeit halber wird er jedoch hier angegeben:

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 gepiped hat.

Ich hoffe es hilft,

0
ATorras