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

252

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

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

codeforester
источник
2
Вот еще несколько интересных тестовых случаев! <a href=" serverfault.com/questions/156470/… для сценария, ожидающего на stdin</a>
2
@ user940324 Правильная ссылка на serverfault.com/q/156470/197218
Palec

Ответы:

385

В чистой оболочке 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
dmckee --- котенок экс-модератора
источник
1
@Kelvin Фрагмент страницы руководства предполагает, что он должен, но эти файловые дескрипторы не назначены по умолчанию.
dmckee --- котенок экс-модератора
41
Для пояснения, -tфлаг указан в POSIX и, следовательно, должен работать для любой POSIX-совместимой оболочки (то есть это не расширение bash). pubs.opengroup.org/onlinepubs/009695399/utilities/test.html
FireFly
Работает и при запуске скрипта как удаленная команда ssh. Лучший ответ и очень простой.
linux_newbie
Я согласен с тем, что после вашего редактирования (редакция 5) ответ будет более четким, чем в редакции 3, а также фактически верным (игнорирование того, что «возврат» используется очень неформально, когда «отпечатки» будут более точными).
Палек
Искал fishснарядный ответ. Использование testаккуратно, но я не могу попробовать пример в скобках, так как это не поддерживается. Попытался обернуть его аналогичным образом begin; ...; end, но это, похоже, не сработало, и просто снова запустил блок положительного кода. Думаю, мне, возможно, придется использовать, statusно это, похоже, не проверяет трубопровод. Я предполагаю, что по сути хочу проверить, не установлен ли STDOUT предыдущей команды / сценария на терминал, благодаря этим уточняющим ответам.
Пизис
126

Не существует надежного способа определить, передаются ли 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.

Дежай Клэйтон
источник
2
Четкая проверка переменных среды или имен процессов - очень ненадежная эвристика. Но не могли бы вы немного рассказать, почему другие эвристики не подходят для этой цели или в чем их проблема? Например, я не вижу разницы в выводе statвызова на / dev / stdin. А почему "${-}"или tty -sне работает? Я также изучил исходный код, catно не смог понять, какая часть делает магию, которую вы не можете сделать в оболочке POSIX. Не могли бы вы рассказать об этом более детально?
josch
30

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

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

Смотрите " man test" или " man bash" и ищите " -t"

гулянка
источник
3
+1 для "man test", потому что / usr / bin / test будет работать даже в оболочке, которая не реализует -t в своем встроенном тесте
Нил Мэйхью
4
Как отметил FireFly в ответе dmckee, оболочка, которая не поддерживает -t, не соответствует POSIX.
scy
См. Также встроенные функции bash help testhelp helpдля получения дополнительной информации), а затем info bashдля получения более подробной информации. Эти команды хороши, если вы когда-нибудь заканчиваете писать сценарии в автономном режиме или просто хотите получить более широкое понимание.
Джоэл Пурра
13

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

#!/bin/bash

if [[ -t 1 ]]; then
    # stdout is a terminal
else
    # stdout is not a terminal
fi
Дэн Молдинг
источник
6

На Солярисе предложение от Деджея Клейтона работает в основном. -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

:# 
sbj3
источник
Интересно, я хотел бы иметь доступ к Solaris для тестирования. Если ваш экземпляр Solaris использует файловую систему "/ proc", существуют более надежные решения, которые включают поиск символических ссылок "/ proc" для stdin, stdout и stderr.
Деджей Клейтон,
1

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

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

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

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

ATorras
источник