298

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

恰当的例子:我想添加转义码来为输出着色,但仅限于交互运行时,而不是在管道时,类似于什么ls --color

4

6 回答 6

461

在纯 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可以是通常的文件描述符分配之一:

于 2009-05-26T15:13:32.313 回答
142

没有万无一失的方法来确定 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'

但它们并不总是有效

但是,当将此命令作为非 TTYssh命令执行时,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 脚本无法判断非 ttyssh命令是否正在通过管道传输。请注意,当最近版本的ssh非 TTY STDIO 开始使用管道时,引入了这种不幸的行为。以前的版本使用套接字,可以通过使用[[ -S ]].

重要的时候

当您要编写行为类似于已编译实用程序(例如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/stdin 文件描述符
  • 通过检查交互模式[[ "${-}" =~ 'i' ]]
  • tty通过和检查 tty 状态tty -s
  • 通过检查ssh状态[[ "$(ps -o comm= -p $PPID)" =~ 'sshd' ]]

请注意,如果您使用的是支持/proc虚拟文件系统的操作系统,您可能会幸运地按照 STDIO 的符号链接来确定是否正在使用管道。但是,/proc它不是跨平台的、与 POSIX 兼容的解决方案。

我对解决这个问题非常感兴趣,所以如果您想到任何其他可能有效的技术,请告诉我,最好是在 Linux 和 BSD 上工作的基于 POSIX 的解决方案。

于 2015-05-29T02:48:25.000 回答
33

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

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

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

于 2009-05-26T15:14:57.333 回答
13

你没有提到你正在使用哪个 shell,但是在 Bash 中,你可以这样做:

#!/bin/bash

if [[ -t 1 ]]; then
    # stdout is a terminal
else
    # stdout is not a terminal
fi
于 2009-05-26T15:21:07.697 回答
8

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

:#
于 2015-06-12T20:41:57.933 回答
1

以下代码(仅在 Linux Bash 4.4 中测试)不应被视为可移植也不推荐,但为了完整起见,这里是:

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

我不知道为什么,但是当 Bash 函数具有标准输入管道时,似乎以某种方式创建了文件描述符“3” 。

于 2019-02-13T11:13:22.763 回答