Jak wykryć, czy mój skrypt powłoki działa przez potok?

252

Jak wykryć ze skryptu powłoki, czy standardowe wyjście jest wysyłane do terminala lub czy jest przesyłane potokowo do innego procesu?

Przykład: chciałbym dodać kody ucieczki, aby pokolorować dane wyjściowe, ale tylko wtedy, gdy są uruchamiane interaktywnie, ale nie po potoku, podobnie jak to ls --colorrobi.

codeforester
źródło
2
Oto kilka interesujących przypadków testowych! <a href=" serverfault.com/questions/156470/... dla skryptu, który czeka na standardowym standardzie</a>
2
@ user940324 Prawidłowy link to serverfault.com/q/156470/197218
Palec

Odpowiedzi:

385

W czystej powłoce POSIX

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

zwraca „terminal”, ponieważ dane wyjściowe są wysyłane do terminala, natomiast

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

zwraca „nie terminal”, ponieważ dane wyjściowe w nawiasach są przesyłane potokowo cat.


-tFlaga jest opisany na stronach człowieka jako

-t fd Prawda, jeśli deskryptor pliku fd jest otwarty i odnosi się do terminala.

... gdzie fdmoże być jedno ze zwykłych przypisań deskryptorów plików:

0:     stdin  
1:     stdout  
2:     stderr
dmckee --- były kot moderator
źródło
1
@Kelvin Fragment strony podręcznika sugeruje, że powinien, ale te deskryptory plików nie są domyślnie przypisywane.
dmckee --- były moderator kociak
41
Aby to wyjaśnić, -tflaga jest określona w POSIX, a zatem powinna działać z każdą powłoką kompatybilną z POSIX (to znaczy nie jest rozszerzeniem bash). pubs.opengroup.org/onlinepubs/009695399/utilities/test.html
FireFly
Działa również podczas uruchamiania skryptu jako zdalnej komendy ssh. Najlepsza odpowiedź w historii i bardzo prosta.
linux_newbie
Zgadzam się, że po twojej edycji (wersja 5) odpowiedź jest wyraźniejsza niż w wersji 3, a także poprawna pod względem faktycznym (ignorowanie tego, że „zwroty” są używane bardzo nieformalnie, a „wydruki” byłyby bardziej precyzyjne).
Palec
Szukałem fishodpowiedzi powłoki. Używanie testjest fajne, ale nie mogę wypróbować nawiasowego przykładu, ponieważ nie jest on obsługiwany. Próbowałem zawinąć go w analogiczny sposób begin; ...; end, ale wydawało się, że to nie działa, i po prostu ponownie uruchomiłem pozytywny blok kodu. Pomyślałem, że będę musiał użyć, statusale to nie wydaje się sprawdzać pod kątem instalacji. Chyba zasadniczo chcę sprawdzić, czy STDOUT poprzedniego polecenia / skryptu nie jest ustawiony na terminalu, dzięki tym wyjaśniającym odpowiedziom.
Pysis
126

Nie ma niezawodnego sposobu ustalenia, czy STDIN, STDOUT lub STDERR są przesyłane do / ze skryptu, głównie z powodu programów takich jak ssh.

Rzeczy, które „normalnie” działają

Na przykład następujące rozwiązanie bash działa poprawnie w interaktywnej powłoce:

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

Ale nie zawsze działają

Jednak podczas wykonywania tego polecenia jako polecenia innego niż TTY ssh, strumienie STD zawsze wyglądają, jakby były przesyłane potokowo. Aby to zademonstrować, używając STDIN, ponieważ jest to łatwiejsze:

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

Dlaczego jest to ważne

Jest to dość duża sprawa, ponieważ sugeruje, że skrypt bash nie ma sposobu, aby stwierdzić, czy sshpolecenie inne niż tty jest przesyłane potokowo, czy nie. Zauważ, że to niefortunne zachowanie zostało wprowadzone, gdy najnowsze wersje sshzaczęły używać potoków dla STDIO innych niż TTY. Wcześniejsze wersje używały gniazd, które MOGĄ być odróżniane od wewnątrz bash przy użyciu [[ -S ]].

Kiedy to ma znaczenie

To ograniczenie zwykle powoduje problemy, gdy chcesz napisać skrypt bash, który ma zachowanie podobne do skompilowanego narzędzia, takiego jak cat. Na przykład catumożliwia następujące elastyczne zachowanie podczas obsługi różnych źródeł wejściowych jednocześnie i jest wystarczająco inteligentny, aby określić, czy odbiera dane wejściowe w potoku niezależnie od tego, czy sshużywany jest tryb TTY czy wymuszony :

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

Możesz zrobić coś takiego tylko wtedy, gdy możesz wiarygodnie ustalić, czy rury są zaangażowane, czy nie. W przeciwnym razie wykonanie polecenia, które czyta STDIN, gdy żadne wejście nie jest dostępne ani z potoków, ani z przekierowania spowoduje zawieszenie skryptu i oczekiwanie na wejście STDIN.

Inne rzeczy, które nie działają

Próbując rozwiązać ten problem, przyjrzałem się kilku technikom, które nie rozwiązały problemu, w tym takim, które obejmują:

  • badanie zmiennych środowiskowych SSH
  • używając statdeskryptorów plików / dev / stdin
  • badanie trybu interaktywnego przez [[ "${-}" =~ 'i' ]]
  • sprawdzanie statusu tty za pomocą ttyitty -s
  • sprawdzanie sshstatusu przez[[ "$(ps -o comm= -p $PPID)" =~ 'sshd' ]]

Pamiętaj, że jeśli używasz systemu operacyjnego obsługującego /procwirtualny system plików, możesz mieć szczęście podążając za dowiązaniami symbolicznymi do STDIO, aby ustalić, czy potok jest używany, czy nie. Jednak /procnie jest wieloplatformowym rozwiązaniem zgodnym z POSIX.

Jestem niezwykle interesujący w rozwiązaniu tego problemu, więc proszę dać mi znać, jeśli pomyślisz o jakiejkolwiek innej technice, która może działać, najlepiej rozwiązaniach opartych na POSIX, które działają zarówno na Linuksie, jak i BSD.

Dejay Clayton
źródło
2
Jasne sprawdzanie zmiennych środowiskowych lub nazw procesów to bardzo zawodna heurystyka. Ale czy mógłbyś nieco wyjaśnić, dlaczego inne heurystyki nie nadają się do tego celu lub jaki jest ich problem? Na przykład nie widzę żadnej różnicy w wynikach statwywołania na / dev / stdin. A dlaczego działa "${-}"czy tty -snie? Zajrzałem także do kodu źródłowego, catale nie zauważyłem, która część robi magię, której nie można zrobić w powłoce POSIX. Mógłbyś to rozwinąć?
josch
30

Polecenie test(wbudowane bash) ma opcję sprawdzenia, czy deskryptor pliku jest tty.

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

Zobacz „ man test” lub „ man bash” i wyszukaj „ -t

Uciecha
źródło
3
+1 za „test człowieka”, ponieważ / usr / bin / test będzie działał nawet w powłoce, która nie implementuje -t w teście wbudowania
Neil Mayhew
4
Jak zauważył FireFly w odpowiedzi dmckee, powłoka, która nie implementuje -t, nie jest zgodna z POSIX.
scy
Zobacz także wbudowane bash help test(i help helpwięcej), a następnie info bashwięcej szczegółowych informacji. Te polecenia są świetne, jeśli kiedykolwiek skończysz pisać skrypty offline lub po prostu chcesz uzyskać szersze zrozumienie.
Joel Purra,
13

Nie wspominasz o używanej powłoce, ale w Bash możesz to zrobić:

#!/bin/bash

if [[ -t 1 ]]; then
    # stdout is a terminal
else
    # stdout is not a terminal
fi
Dan Molding
źródło
6

W Solarisie sugestia Dejay Claytona działa głównie. -P nie reaguje zgodnie z życzeniem.

bash_redir_test.sh wygląda następująco:

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

W systemie Linux działa świetnie:

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

W systemie 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
źródło
Interesujące, chciałbym mieć dostęp do Solaris, aby przetestować. Jeśli instancja systemu Solaris korzysta z systemu plików „/ proc”, istnieją bardziej niezawodne rozwiązania, które obejmują wyszukiwanie dowiązań symbolicznych „/ proc” dla stdin, stdout i stderr.
Dejay Clayton,
1

Poniższy kod (przetestowany tylko w Linuksie bash 4.4) nie powinien być uważany za przenośny ani zalecany , ale ze względu na kompletność jest to:

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

Nie wiem dlaczego, ale wydaje się, że deskryptor pliku „3” jest w jakiś sposób tworzony, gdy funkcja bash ma potok STDIN.

Mam nadzieję, że to pomoże,

ATorras
źródło