Как я могу обнаружить из оболочки script, если ее стандартный вывод предназначен для терминала или если он подключен к другому процессу? (В данном случае я хотел бы добавить escape-коды для раскраски вывода, но только при интерактивном запуске, но не в том случае, когда используется канал, аналогично тому, что делает ls --color
.)
Как определить, работает ли моя оболочка script через канал?
Ответ 1
В чистой оболочке 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 описывается в man-страницах как
-t fd Истина, если файловый дескриптор fd открыт и ссылается на терминал.
... где fd
может быть одним из обычных назначений дескриптора файла:
0: stdin
1: stdout
2: stderr
Ответ 2
Нет надежного способа определить, передаются ли STDIN, STDOUT или STDERR в/из вашего script, в основном из-за таких программ, как 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 script указать, передается ли команда non-tty ssh
или нет. Обратите внимание, что это неудачное поведение было введено, когда последние версии ssh
начали использовать трубы для не-TTY STDIO. В предыдущих версиях использовались сокеты, которые МОГУТ быть дифференцированы из bash с помощью [[ -S ]]
.
Когда это имеет значение
Это ограничение обычно вызывает проблемы, когда вы хотите написать bash script, который имеет поведение, подобное скомпилированной утилите, например cat
. Например, cat
допускает следующее гибкое поведение при обработке различных источников входного сигнала одновременно и достаточно умен, чтобы определить, получает ли он входной канал, независимо от того, используется ли не TTY или принудительное TTY ssh
:
ssh -t localhost 'echo piped | cat - <( echo substituted )'
ssh -T localhost 'echo piped | cat - <( echo substituted )'
Вы можете сделать только что-то подобное, если можете надежно определить, задействованы ли трубы или нет. В противном случае выполнение команды, которая считывает STDIN, когда входной сигнал не доступен ни из одного из каналов, ни из перенаправления, приведет к тому, что w971 висит и ожидает ввода STDIN.
Другие вещи, которые не работают
В попытке решить эту проблему я рассмотрел несколько методов, которые не могут решить проблему, включая те, которые включают:
- проверка переменных среды SSH
- с помощью
stat
в дескрипторах файла /dev/stdin - изучение интерактивного режима через
[[ "${-}" =~ 'i' ]]
- проверка состояния tty с помощью
tty
иtty -s
- проверка состояния
ssh
с помощью[[ "$(ps -o comm= -p $PPID)" =~ 'sshd' ]]
Обратите внимание, что если вы используете ОС, поддерживающую виртуальную файловую систему /proc
, вам может понадобиться следовать символическим ссылкам для STDIO, чтобы определить, используется ли канал или нет. Однако /proc
не является межплатформенным решением, совместимым с POSIX.
Мне очень интересно в решении этой проблемы, поэтому, пожалуйста, дайте мне знать, если вы думаете о какой-либо другой технике, которая может работать, предпочтительно на основе POSIX-решений, которые работают как на Linux, так и на BSD.
Ответ 3
Команда test
(встроенная в bash
), имеет возможность проверить, является ли дескриптор файла tty.
if [ -t 1 ]; then
# stdout is a tty
fi
См. "man test
" или "man bash
" и выполните поиск "-t
"
Ответ 4
Вы не указываете, какую оболочку используете, но в Bash вы можете сделать это:
#!/bin/bash
if [[ -t 1 ]]; then
# stdout is a terminal
else
# stdout is not a terminal
fi
Ответ 5
В Solaris предложение от Dejay Clayton работает в основном. -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
В 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
:#