Как определить, работает ли мой сценарий оболочки через канал?
Как определить из сценария оболочки, отправляется ли его стандартный вывод на терминал или он передается другому процессу?
Показательный пример: я хотел бы добавить escape-коды для раскрашивания вывода, но только при интерактивном запуске, а не при передаче по конвейеру, аналогично тому, что ls --color
делает.
6 ответов
В чистой оболочке 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
Не существует надежного способа определить, передаются ли 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
в /dev/stdin файловых дескрипторах - изучение интерактивного режима через
[[ "${-}" =~ 'i' ]]
- проверка статуса tty через
tty
а такжеtty -s
- изучения
ssh
статус через[[ "$(ps -o comm= -p $PPID)" =~ 'sshd' ]]
Обратите внимание, что если вы используете ОС, которая поддерживает /proc
Виртуальная файловая система, вам может повезти, пройдя по символическим ссылкам для STDIO, чтобы определить, используется ли канал или нет. Тем не мение, /proc
не является кроссплатформенным, POSIX-совместимым решением.
Я чрезвычайно заинтересован в решении этой проблемы, поэтому, пожалуйста, дайте мне знать, если вы думаете о какой-либо другой технике, которая могла бы работать, предпочтительно решениях на основе POSIX, которые работают как на Linux, так и на BSD.
Команда test
(встроенный в bash
), имеет возможность проверить, является ли дескриптор файла tty.
if [ -t 1 ]; then
# stdout is a tty
fi
Увидеть "man test
" или же "man bash
"и искать"-t
"
Вы не упоминаете, какую оболочку вы используете, но в Bash вы можете сделать это:
#!/bin/bash
if [[ -t 1 ]]; then
# stdout is a terminal
else
# stdout is not a terminal
fi
На Солярисе предложение от Деджея Клейтона работает в основном. -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
:#
Следующий код (протестированный только в linux bash 4.4) не следует рассматривать как переносимый и не рекомендуемый, но для полноты здесь он таков:
ls /proc/$$/fdinfo/* >/dev/null 2>&1 || grep -q 'flags: 00$' /proc/$$/fdinfo/0 && echo "pipe detected"
Я не знаю почему, но кажется, что файловый дескриптор "3" каким-то образом создается, когда функция bash имеет канал STDIN.
Надеюсь, поможет,