如何检测我的Shell脚本是否正在通过管道运行?


252

如何从Shell脚本中检测其标准输出是发送到终端还是通过管道传输到另一个进程?

恰当的例子是:我想添加转义代码以使输出着色,但是仅当以交互方式运行时,而不是通过管道运行时(类似于这样ls --color做)。


2
这是一些更有趣的测试用例!< ahref=" serverfault.com/questions/156470/…表示正在等待标准输入的脚本</ a>

2
@ user940324正确的链接是serverfault.com/q/156470/197218
Palec 2014年

Answers:


385

在纯POSIX外壳中,

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:     stdin  
1:     stdout  
2:     stderr

1
@Kelvin那里的手册页片段建议这样做,但是默认情况下不分配这些文件描述符。
dmckee ---前主持人小猫,

41
为了清楚起见,该-t标志是在POSIX中指定的,因此应适用于任何POSIX兼容的外壳(也就是说,它不是bash扩展名)。 pubs.opengroup.org/onlinepubs/009695399/utilities/test.html
FireFly

在将脚本作为ssh远程命令运行时也可以使用。最佳答案,而且非常简单。
linux_newbie 2016年

我同意,在您进行编辑(修订版5)之后,答案比修订版3更为清晰,而且实际上是正确的(忽略“返回”在非正式情况下使用,“印刷”更为精确)。
Palec

寻找fish壳的答案。使用起来test很简洁,但是我不能尝试带括号的示例,因为它不受支持。尝试将其包装成类似形式begin; ...; end,但这似乎没有用,只是再次运行肯定代码块。以为我可能需要使用,status但这似乎不需要检查管道。由于这些明确的答案,我想我本质上是想检查先前命令/脚本的STDOUT是否未设置到终端。
Pysis

126

没有万无一失的方法来确定STDIN,STDOUT或STDERR是否通过管道传递到脚本或从脚本通过管道传递,这主要是由于诸如之类的程序引起的ssh

“正常”工作的事情

例如,以下bash解决方案可在交互式外壳程序中正常工作:

[[ -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在同时处理各种输入源时,允许以下灵活的行为,并且无论使用了非TTY还是强制TTY,它都足够聪明地确定是否接收管道输入ssh

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兼容解决方案。

我对解决这个问题非常感兴趣,因此,如果您想到其他可行的技术,请让我知道,最好是同时适用于Linux和BSD的基于POSIX的解决方案。


2
清楚地检查环境变量或进程名称是非常不可靠的启发式方法。但是您能否解释一下为什么其他启发式方法不适合此目的或它们的问题是什么?例如,stat在/ dev / stdin上的调用输出中没有看到任何区别。又为何"${-}"还是tty -s不行?我还研究了的源代码,cat但看不到哪一部分在做您无法在POSIX shell中完成的工作。你能详细谈谈?
约瑟(josch)

30

命令test(内置于中bash)具有检查文件描述符是否为tty的选项。

if [ -t 1 ]; then
    # stdout is a tty
fi

请参阅“ man test”或“ man bash”并搜索“ -t


3
+1表示“人工测试”,因为/ usr / bin / test甚至可以在内置测试中未实现-t的外壳中正常工作
Neil Mayhew 2013年

4
如dmckee的答案中FireFly所指出的,没有实现-t的shell不符合POSIX。
scy 13-10-8

另请参见bash的内置函数help test(以及help help更多信息),然后info bash获取更深入的信息。如果您最终需要离线编写脚本,或者只是想获得更广泛的了解,那么这些命令非常有用。
乔尔·普拉

13

您没有提到要使用哪个shell,但是在Bash中,您可以执行以下操作:

#!/bin/bash

if [[ -t 1 ]]; then
    # stdout is a terminal
else
    # stdout is not a terminal
fi

6

在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

:# 

有趣的是,我希望我可以使用Solaris进行测试。如果您的Solaris实例使用“ / proc”文件系统,则存在更可靠的解决方案,其中包括搜索stdin,stdout和stderr的“ / proc”符号链接。
Dejay Clayton

1

以下代码(仅在linux bash 4.4中进行了测试)不应被视为可移植或推荐的,但出于完整性考虑,它是:

ls /proc/$$/fdinfo/* >/dev/null 2>&1 || grep -q 'flags: 00$' /proc/$$/fdinfo/0 && echo "pipe detected"

我不知道为什么,但是当bash函数通过管道传输STDIN时,似乎以某种方式创建了文件描述符“ 3”。

希望能帮助到你,

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.