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