如何检测我的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' ]]
  • 通过ttytty -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 :#