如何检测shell脚本是否通过管道运行?

ktecyv1j  于 2023-08-07  发布在  Shell
关注(0)|答案(6)|浏览(136)

如何从shell脚本中检测它的标准输出是发送到终端还是通过管道发送到另一个进程?
以下是一个典型的例子:我想添加转义码来对输出进行着色,但只在交互式运行时使用,而不是在管道化时使用,类似于ls --color所做的。

50pmv0ei

50pmv0ei1#

在纯POSIX shell中,

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


会传回“not a terminal”,因为括号元素的输出会以管道传送至cat
手册页中对-t标志进行了如下描述
-t fd如果文件描述符fd是打开的并且指向一个终端,则为True。
......其中fd可以是常用的文件描述符分配之一:

  • 0:标准输入
  • 1:标准输出
  • 2:标准误
doinxwow

doinxwow2#

没有一种万无一失的方法来确定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脚本无法判断是否正在通过管道传输非tty ssh命令。请注意,当ssh的最新版本开始为非TTY STDIO使用管道时,引入了这种不幸的行为。以前的版本使用套接字,可以通过使用[[ -S ]]与bash中的套接字区分开来。

When it matters

当您想要编写一个行为类似于编译实用程序(如cat)的bash脚本时,这种限制通常会导致问题。例如,cat在同时处理各种输入源时允许以下灵活行为,并且无论使用的是非TTY还是强制TTY ssh,它都足够智能,可以确定是否正在接收管道输入:

ssh -t localhost 'echo piped | cat - <( echo substituted )'
ssh -T localhost 'echo piped | cat - <( echo substituted )'


只有在能够可靠地确定是否涉及管道的情况下,才能执行类似的操作。否则,在管道或重定向中没有输入时执行读取STDIN的命令将导致脚本挂起并等待STDIN输入。

其他不起作用的东西

为了解决这个问题,我研究了几种无法解决问题的技术,其中包括:

  • 检查SSH环境变量
  • 在/dev/stdin文件描述符上使用stat
  • 通过[[ "${-}" =~ 'i' ]]检查交互模式
  • 通过ttytty -s检查tty状态
  • 通过[[ "$(ps -o comm= -p $PPID)" =~ 'sshd' ]]检查ssh状态

请注意,如果您使用的操作系统支持/proc虚拟文件系统,那么您可能很幸运地通过STDIO的符号链接来确定是否使用了管道。但是,/proc不是一个跨平台、POSIX兼容的解决方案。
我对解决这个问题非常感兴趣,所以如果您想到任何其他可行的技术,请告诉我,最好是在Linux和BSD上都能工作的基于POSIX的解决方案。

ny6fqffe

ny6fqffe3#

命令test(内置于Bash中)有一个选项来检查文件描述符是否是tty。

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

字符串
查看“man test”或“man bash”并搜索“-t”。

7bsow1i6

7bsow1i64#

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

#!/bin/bash

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

字符串

7qhs6swi

7qhs6swi5#

Solaris上,Dejay克莱顿的建议基本有效。-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

:#

fykwrbwg

fykwrbwg6#

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

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

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

相关问题