Как определить, работает ли мой сценарий оболочки через канал? - PullRequest
222 голосов
/ 26 мая 2009

Как определить из сценария оболочки, отправляется ли его стандартный вывод на терминал или передается ли он другому процессу?

Показательный пример: я хотел бы добавить escape-коды для раскрашивания вывода, но только при интерактивном запуске, а не по трубопроводу, аналогично тому, что делает ls --color.

Ответы [ 6 ]

330 голосов
/ 26 мая 2009

В чистой оболочке POSIX,

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

возвращает «терминал», потому что вывод отправляется на ваш терминал, тогда как

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

возвращает «не терминал», потому что вывод скобок передается по каналу cat.


Флаг -t описывается на страницах руководства как

-t fd Истина, если дескриптор файла fd открыт и ссылается на терминал.

... где fd может быть одним из обычных назначений дескриптора файла:

0:     stdin  
1:     stdout  
2:     stderr
106 голосов
/ 29 мая 2015

Не существует надежного способа определить, передаются ли STDIN, STDOUT или STDERR в / из вашего сценария, в основном из-за таких программ, как ssh.

Вещи, которые "нормально" работают

Например, следующее решение bash правильно работает в интерактивной оболочке:

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

Но они не всегда работают

Однако при выполнении этой команды как команды, отличной от TTY ssh, потоки STD всегда выглядят так, как будто они передаются по конвейеру. Чтобы продемонстрировать это, используйте STDIN, потому что это проще:

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

Почему это важно

Это довольно большое дело, потому что это означает, что bash-скрипт не может определить, передается ли не-tty команда ssh или нет. Обратите внимание, что это неудачное поведение было введено, когда последние версии ssh начали использовать каналы для не-TTY STDIO. В предыдущих версиях использовались сокеты, которые МОГУТ различать изнутри bash с помощью [[ -S ]].

Когда это имеет значение

Это ограничение обычно вызывает проблемы, когда вы хотите написать bash-скрипт, который ведет себя подобно скомпилированной утилите, такой как cat. Например, cat допускает следующее гибкое поведение при одновременной обработке различных источников ввода и достаточно умен, чтобы определить, получает ли он ввод по каналу независимо от того, используется ли не TTY или принудительный TTY ssh:

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

Вы можете сделать что-то подобное, только если сможете достоверно определить, задействованы ли трубы или нет. В противном случае выполнение команды, которая читает STDIN, когда нет данных, доступных из каналов или перенаправления, приведет к зависанию сценария и ожиданию ввода STDIN.

Другие вещи, которые не работают

Пытаясь решить эту проблему, я рассмотрел несколько методов, которые не смогли решить проблему, в том числе:

  • проверка переменных среды SSH
  • с использованием stat on / dev / stdin файловых дескрипторов
  • просмотр интерактивного режима через [[ "${-}" =~ 'i' ]]
  • проверка состояния tty через tty и tty -s
  • проверка ssh статуса через [[ "$(ps -o comm= -p $PPID)" =~ 'sshd' ]]

Обратите внимание, что если вы используете ОС, которая поддерживает виртуальную файловую систему /proc, вам может повезти, пройдя по символическим ссылкам для STDIO, чтобы определить, используется ли канал или нет. Однако /proc не является кроссплатформенным POSIX-совместимым решением.

Я чрезвычайно заинтересован в решении этой проблемы, поэтому, пожалуйста, дайте мне знать, если вы думаете о каком-либо другом методе, который может работать, предпочтительно решениях на основе POSIX, которые работают как в Linux, так и в BSD.

27 голосов
/ 26 мая 2009

Команда test (встроенная в bash), имеет возможность проверить, является ли дескриптор файла tty.

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

См. "man test" или "man bash" и найдите "-t"

11 голосов
/ 26 мая 2009

Вы не упоминаете, какую оболочку вы используете, но в Bash вы можете сделать это:

#!/bin/bash

if [[ -t 1 ]]; then
    # stdout is a terminal
else
    # stdout is not a terminal
fi
4 голосов
/ 12 июня 2015

На Солярисе предложение от Деджея Клейтона работает в основном. -P не отвечает как требуется.

bash_redir_test.sh выглядит так:

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

В Linux он прекрасно работает:

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

На Солярисе:

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

:# 
1 голос
/ 13 февраля 2019

Следующий код (проверен только в linux bash 4.4) не следует рассматривать как переносимый или рекомендованный , но для полноты здесь он выглядит так:

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

Я не знаю почему, но кажется, что файловый дескриптор "3" каким-то образом создается, когда функция bash имеет канал STDIN.

Надеюсь, это поможет,

...