如何从 shell 脚本中检测其标准输出是否正在发送到终端或是否通过管道传输到另一个进程?
恰当的例子:我想添加转义码来为输出着色,但仅限于交互运行时,而不是在管道时,类似于什么ls --color
。
在纯 POSIX shell 中,
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
可以是通常的文件描述符分配之一:
没有万无一失的方法来确定 STDIN、STDOUT 或 STDERR 是否正在通过管道传输到您的脚本/从您的脚本传输,这主要是因为ssh
.
例如,以下 bash 解决方案在交互式 shell 中可以正常工作:
[[ -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'
但是,当将此命令作为非 TTYssh
命令执行时,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 脚本无法判断非 ttyssh
命令是否正在通过管道传输。请注意,当最近版本的ssh
非 TTY STDIO 开始使用管道时,引入了这种不幸的行为。以前的版本使用套接字,可以通过使用[[ -S ]]
.
当您要编写行为类似于已编译实用程序(例如cat
. 例如,cat
在同时处理各种输入源时允许以下灵活行为,并且无论使用非 TTY 还是强制 TTY 都足够聪明地确定它是否正在接收管道输入ssh
:
ssh -t localhost 'echo piped | cat - <( echo substituted )'
ssh -T localhost 'echo piped | cat - <( echo substituted )'
如果您可以可靠地确定是否涉及管道,您只能执行类似的操作。否则,当管道或重定向没有可用输入时执行读取 STDIN 的命令将导致脚本挂起并等待 STDIN 输入。
在尝试解决这个问题时,我研究了几种无法解决问题的技术,包括以下技术:
stat
/dev/stdin 文件描述符[[ "${-}" =~ 'i' ]]
tty
通过和检查 tty 状态tty -s
ssh
状态[[ "$(ps -o comm= -p $PPID)" =~ 'sshd' ]]
请注意,如果您使用的是支持/proc
虚拟文件系统的操作系统,您可能会幸运地按照 STDIO 的符号链接来确定是否正在使用管道。但是,/proc
它不是跨平台的、与 POSIX 兼容的解决方案。
我对解决这个问题非常感兴趣,所以如果您想到任何其他可能有效的技术,请告诉我,最好是在 Linux 和 BSD 上工作的基于 POSIX 的解决方案。
你没有提到你正在使用哪个 shell,但是在 Bash 中,你可以这样做:
#!/bin/bash
if [[ -t 1 ]]; then
# stdout is a terminal
else
# stdout is not a terminal
fi
在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
:#
以下代码(仅在 Linux Bash 4.4 中测试)不应被视为可移植也不推荐,但为了完整起见,这里是:
ls /proc/$$/fdinfo/* >/dev/null 2>&1 || grep -q 'flags: 00$' /proc/$$/fdinfo/0 && echo "pipe detected"
我不知道为什么,但是当 Bash 函数具有标准输入管道时,似乎以某种方式创建了文件描述符“3” 。