如何从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
可以是通常的文件描述符分配之一:
0: stdin 1: stdout 2: stderr
没有万无一失的方法可以确定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脚本无法判断是否ssh
正在管道非tty 命令.请注意,当最近版本的ssh
开始使用非TTY STDIO管道时,会引入这种不幸的行为.以前的版本使用套接字,可以通过使用来区分bash [[ -S ]]
.
当您要编写具有类似于已编译实用程序的行为的bash脚本时,此限制通常会导致问题,例如cat
.例如,cat
允许以下灵活行为同时处理各种输入源,并且足够聪明以确定它是否正在接收管道输入,无论是否ssh
正在使用非TTY或强制TTY :
ssh -t localhost 'echo piped | cat - <( echo substituted )' ssh -T localhost 'echo piped | cat - <( echo substituted )'
如果能够可靠地确定管道是否涉及,您只能做类似的事情.否则,当没有来自管道或重定向的输入时,执行读取STDIN的命令将导致脚本挂起并等待STDIN输入.
在尝试解决这个问题时,我已经研究了几种无法解决问题的技术,包括涉及的问题:
检查SSH环境变量
使用stat
在/ dev /标准输入文件描述符
通过检查交互模式 [[ "${-}" =~ 'i' ]]
通过tty
和检查tty状态tty -s
ssh
通过审查状态[[ "$(ps -o comm= -p $PPID)" =~ 'sshd' ]]
请注意,如果您使用的是支持/proc
虚拟文件系统的操作系统,则可以按照STDIO的符号链接来确定是否正在使用管道.但是,/proc
它不是跨平台,POSIX兼容的解决方案.
我非常有兴趣解决这个问题,所以如果你想到任何其他可能有效的技术,请告诉我,最好是基于POSIX的解决方案,适用于Linux和BSD.
命令test
(内置bash
)有一个选项来检查文件描述符是否为tty.
if [ -t 1 ]; then # stdout is a tty fi
请参阅" man test
"或" man bash
"并搜索" -t
"
你没有提到你正在使用哪个shell,但在Bash中,你可以这样做:
#!/bin/bash if [[ -t 1 ]]; then # stdout is a terminal else # stdout is not a terminal fi