如何检测我的shell脚本是否通过pipe道运行?
如何从shell脚本中检测其标准输出是否定位到terminal或者是否通过pipe道连接到另一个进程? (例子:我想添加转义码来着色输出,但是只有在交互式运行时,而不是在input时,类似于ls --color
所做的。
在一个纯粹的POSIX shell中,
if [ -t 1 ] ; then echo terminal; else echo "not a terminal"; fi
返回“terminal”,因为输出发送到您的terminal,而
(if [ -t 1 ] ; then echo terminal; else echo "not a terminal"; fi) | cat
返回“不是terminal”,因为括号的输出是通过pipe道传给cat
。
-t标志在手册页中被描述为
-t fd如果文件描述符fd是开放的,则引用terminal。
…其中fd
可以是通常的文件描述符分配之一:
0: stdin 1: stdout 2: stderr
有没有简单的方法来确定STDIN,STDOUT,或STDERR是否正在从您的脚本pipe道,主要是因为像ssh
程序。
事情“正常”的工作
例如,下面的bash解决scheme在交互式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
命令时,STDstream总是看起来像他们正在pipe道。 为了演示这一点,使用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
命令是否被pipe理。 请注意,当最近版本的ssh
开始使用非TTY STDIO的pipe道时,引入了这种不幸的行为。 之前的版本使用套接字,可以通过使用[[ -S ]]
在bash中区分它们。
什么时候重要
这个限制通常会导致问题,当你想写一个行为类似于一个编译的工具,如cat
的bash脚本。 例如, cat
允许以下灵活的行为来同时处理各种input源,并且足够聪明以确定是否正在接收pipe道input,而不pipe是否使用非TTY或强制TTY ssh
:
ssh -t localhost 'echo piped | cat - <( echo substituted )' ssh -T localhost 'echo piped | cat - <( echo substituted )'
你只能这样做,如果你可以可靠地确定是否涉及pipe道。 否则,执行一个读取STDIN的命令,当没有input可用于pipe道或redirect时,将导致脚本挂起并等待STDINinput。
其他的东西不起作用
为了解决这个问题,我研究了一些不能解决问题的技术,其中包括:
- 检查SSH环境variables
- 在/ dev / stdin文件描述符上使用
stat
- 通过
[[ "${-}" =~ 'i' ]]
检查交互模式[[ "${-}" =~ 'i' ]]
- 通过
tty
和tty -s
检查tty状态 - 通过
[[ "$(ps -o comm= -p $PPID)" =~ 'sshd' ]]
检查ssh
状态
请注意,如果您使用的是支持/proc
虚拟文件系统的操作系统,那么您可能需要遵循STDIO的符号链接来确定是否正在使用pipe道。 但是, /proc
不是一个跨平台,兼容POSIX的解决scheme。
我在解决这个问题上非常有趣,所以请让我知道,如果你想到其他任何可能工作的技术,最好是在Linux和BSD上工作的基于POSIX的解决scheme。
命令test
(内置于bash
)可以select检查文件描述符是否为tty。
if [ -t 1 ]; then # stdout is a tty fi
请参阅“ man test
”或“ man bash
”并search“ -t
”
你没有提到你正在使用哪个shell,但是在Bash中,你可以这样做:
#!/bin/bash if [[ -t 1 ]]; then # stdout is a terminal else # stdout is not a terminal fi
在Solaris上,Dejay Clayton的build议主要工作。 -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 :#