简体   繁体   English

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

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

How do I detect from within a shell script if its standard output is being sent to a terminal or if it's piped to another process? 如何从shell脚本中检测其标准输出是否被发送到终端或者是否通过管道传输到另一个进程?

The case in point: I'd like to add escape codes to colorize output, but only when run interactively, but not when piped, similar to what ls --color does. 举个例子:我想添加转义代码来着色输出,但是只有在交互式运行时,而不是在管道输出时,类似于ls --color功能。

In a pure POSIX shell, 在纯POSIX shell中,

if [ -t 1 ] ; then echo terminal; else echo "not a terminal"; fi

returns "terminal", because the output is sent to your terminal, whereas 返回“终端”,因为输出发送到您的终端,而

(if [ -t 1 ] ; then echo terminal; else echo "not a terminal"; fi) | cat

returns "not a terminal", because the output of the parenthetic is piped to cat . 返回“不是终端”,因为括号的输出通过管道输送到cat


The -t flag is described in man pages as -t标志在手册页中描述为

-t fd True if file descriptor fd is open and refers to a terminal. -t fd如果文件描述符fd打开并引用终端,则为真。

... where fd can be one of the usual file descriptor assignments: ...其中fd可以是通常的文件描述符分配之一:

0:     stdin  
1:     stdout  
2:     stderr

There is no foolproof way to determine if STDIN, STDOUT, or STDERR are being piped to/from your script, primarily because of programs like ssh . 没有万无一失的方法可以确定STDIN,STDOUT或STDERR是否通过管道进出脚本,主要是因为像ssh这样的程序。

Things that "normally" work “正常”工作的事情

For example, the following bash solution works correctly in an interactive shell: 例如,以下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'

But they don't always work 但他们并不总是有效

However, when executing this command as a non-TTY ssh command, STD streams always looks like they are being piped. 但是,当执行此命令作为非TTY ssh命令时,STD流看起来总是看起来像是被管道传输。 To demonstrate this, using STDIN because it's easier: 为了证明这一点,使用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 ${?}'

Why it matters 为什么重要

This is a pretty big deal, because it implies that there is no way for a bash script to tell whether a non-tty ssh command is being piped or not. 这是一个非常重要的事情,因为它意味着bash脚本无法判断是否正在管道非tty ssh命令。 Note that this unfortunate behavior was introduced when recent versions of ssh started using pipes for non-TTY STDIO. 请注意,当最新版本的ssh开始使用非TTY STDIO管道时,会引入这种不幸的行为。 Prior versions used sockets, which COULD be differentiated from within bash by using [[ -S ]] . 以前的版本使用套接字,可以通过使用[[ -S ]]来区分bash。

When it matters 什么时候重要

This limitation normally causes problems when you want to write a bash script that has behavior similar to a compiled utility, such as cat . 当您要编写具有与已编译实用程序类似的行为的bash脚本(例如cat时,此限制通常会导致问题。 For example, cat allows the following flexible behavior in handling various input sources simultaneously, and is smart enough to determine whether it is receiving piped input regardless of whether non-TTY or forced-TTY ssh is being used: 例如, cat允许在同时处理各种输入源时具有以下灵活行为,并且足够智能以确定它是否正在接收管道输入,无论是否正在使用非TTY或强制TTY ssh

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

You can only do something like that if you can reliably determine if pipes are involved or not. 如果能够可靠地确定管道是否涉及,您只能做类似的事情。 Otherwise, executing a command that reads STDIN when no input is available from either pipes or redirection will result in the script hanging and waiting for STDIN input. 否则,当没有来自管道或重定向的输入时,执行读取STDIN的命令将导致脚本挂起并等待STDIN输入。

Other things that don't work 其他不起作用的东西

In trying to solve this problem, I've looked at several techniques that fail to solve the problem, including ones that involve: 在尝试解决这个问题时,我已经研究了几种无法解决问题的技术,包括涉及的问题:

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

Note that if you are using an OS that supports the /proc virtual filesystem, you might have luck following the symbolic links for STDIO to determine whether a pipe is being used or not. 请注意,如果您使用的是支持/proc虚拟文件系统的操作系统,则可以按照STDIO的符号链接来确定是否正在使用管道。 However, /proc is not a cross-platform, POSIX-compatible solution. 但是, /proc不是跨平台,POSIX兼容的解决方案。

I'm extremely interesting in solving this problem, so please let me know if you think of any other technique that might work, preferably POSIX-based solutions that work on both Linux and BSD. 我非常有兴趣解决这个问题,所以如果你想到任何其他可能有效的技术,请告诉我,最好是基于POSIX的解决方案,适用于Linux和BSD。

The command test (builtin in bash ), has an option to check if a file descriptor is a tty. 命令test (内置于bash )有一个选项来检查文件描述符是否为tty。

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

See " man test " or " man bash " and search for " -t " 参见“ man test ”或“ man bash ”并搜索“ -t

You don't mention which shell you are using, but in Bash, you can do this: 你没有提到你正在使用哪个shell,但在Bash中,你可以这样做:

#!/bin/bash

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

On Solaris, the suggestion from Dejay Clayton works mostly. 在Solaris上,Dejay Clayton的建议主要起作用。 The -p does not respond as desired. -p没有按要求响应。

bash_redir_test.sh looks like: 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'

On Linux, it works great: 在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

On Solaris: 在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

:# 

The following code (tested only in linux bash 4.4) should not be considered portable nor recommended , but for the sake of completeness here it is: 以下代码(仅在linux bash 4.4中测试) 不应被视为可移植或推荐 ,但为了完整起见,这里是:

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

I don't know why, but it seems that file descriptor "3" is somehow created when a bash function has STDIN piped. 我不知道为什么,但似乎文件描述符“3”以某种方式在bash函数具有STDIN管道时创建。

Hope it helps, 希望能帮助到你,

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

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