繁体   English   中英

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

[英]How to detect if my shell script is running through a pipe?

如何从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脚本无法判断是否正在管道非tty ssh命令。 请注意,当最新版本的ssh开始使用非TTY STDIO管道时,会引入这种不幸的行为。 以前的版本使用套接字,可以通过使用[[ -S ]]来区分bash。

什么时候重要

当您要编写具有与已编译实用程序类似的行为的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环境变量
  • 在/ dev / stdin文件描述符上使用stat
  • 通过[[ "${-}" =~ 'i' ]]检查交互模式
  • 通过ttytty -s检查tty状态
  • 通过[[ "$(ps -o comm= -p $PPID)" =~ 'sshd' ]]检查ssh状态

请注意,如果您使用的是支持/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

在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

:# 

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

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

我不知道为什么,但似乎文件描述符“3”以某种方式在bash函数具有STDIN管道时创建。

希望能帮助到你,

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM