我如何从一个shell脚本中检测,如果它的标准输出被发送到终端或如果它被管道到另一个进程?
举个例子:我想添加转义码来着色输出,但只在交互运行时,而不是在管道运行时,类似于ls——color。
我如何从一个shell脚本中检测,如果它的标准输出被发送到终端或如果它被管道到另一个进程?
举个例子:我想添加转义码来着色输出,但只在交互运行时,而不是在管道运行时,类似于ls——color。
当前回答
在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
:#
其他回答
在纯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
返回“非终端”,因为插入元素的输出被管道传输给cat。
-t标志在手册页中描述为
-t fd如果打开文件描述符fd并指向终端,则为True。
... fd可以是常见的文件描述符赋值之一:
0:标准输入 1:标准输出 2:标准误差
命令test(内置在Bash中)有一个选项来检查文件描述符是否是tty。
if [ -t 1 ]; then
# Standard output is a tty
fi
参见“man test”或“man bash”,搜索“-t”。
在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
:#
没有一种万无一失的方法来确定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使用管道时引入的。以前的版本使用套接字,可以在bash中使用[[-S]]进行区分。
重要的时候
当您希望编写行为类似于编译实用程序(如cat)的bash脚本时,这种限制通常会导致问题。例如,cat在同时处理各种输入源时允许以下灵活的行为,并且足够智能,可以确定它是否正在接收管道输入,而不管是否使用非tty或强制tty ssh:
ssh -t localhost 'echo piped | cat - <( echo substituted )'
ssh -T localhost 'echo piped | cat - <( echo substituted )'
只有当您能够可靠地确定是否涉及管道时,您才能这样做。否则,在没有管道或重定向输入时执行读取STDIN的命令将导致脚本挂起并等待STDIN输入。
其他不起作用的东西
为了解决这个问题,我研究了几种无法解决问题的技术,其中包括:
检查SSH环境变量 在/dev/stdin文件描述符上使用stat 通过[["${-}" =~ 'i']]检查交互模式 通过tty和tty -s检查tty状态 通过[["$(ps -o comm= -p $PPID)"检查ssh状态=~ 'sshd']]
注意,如果您使用的是支持/proc虚拟文件系统的操作系统,那么您可能会幸运地按照STDIO的符号链接来确定是否正在使用管道。但是,/proc不是一个跨平台的、与posix兼容的解决方案。
我对解决这个问题非常感兴趣,所以如果您想到任何其他可行的技术,最好是在Linux和BSD上都能工作的基于posix的解决方案,请告诉我。
下面的代码(仅在Linux Bash 4.4中测试)不应该被认为是可移植的,也不推荐,但为了完整性,这里是:
ls /proc/$$/fdinfo/* >/dev/null 2>&1 || grep -q 'flags: 00$' /proc/$$/fdinfo/0 && echo "pipe detected"
我不知道为什么,但当Bash函数具有标准输入管道时,似乎以某种方式创建了文件描述符“3”。