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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/911168/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-09 18:12:57  来源:igfitidea点击:

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

bashshellpipe

提问by dmckee --- ex-moderator kitten

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 --colordoes.

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

回答by dmckee --- ex-moderator kitten

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 -tflag 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 fdcan be one of the usual file descriptor assignments:

... wherefd可以是通常的文件描述符分配之一:

0:     stdin  
1:     stdout  
2:     stderr

回答by Dejay Clayton

There is no foolproofway 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 sshcommand, STD streams alwayslooks like they are being piped. To demonstrate this, using STDIN because it's easier:

但是,当将此命令作为非 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 ${?}'

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 sshcommand is being piped or not. Note that this unfortunate behavior was introduced when recent versions of sshstarted using pipes for non-TTY STDIO. Prior versions used sockets, which COULD be differentiated from within bash by using [[ -S ]].

这是一个非常大的问题,因为它意味着 bash 脚本无法判断是否ssh正在通过管道传输非 tty命令。请注意,当最近版本ssh开始使用非 TTY STDIO 的管道时,引入了这种不幸的行为。以前的版本使用套接字,可以通过使用[[ -S ]].

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. For example, catallows 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 sshis being used:

当您想要编写行为类似于已编译实用程序的 bash 脚本时,此限制通常会导致问题,例如cat. 例如,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
  • using staton /dev/stdin file descriptors
  • examining interactive mode via [[ "${-}" =~ 'i' ]]
  • examining tty status via ttyand tty -s
  • examining sshstatus via [[ "$(ps -o comm= -p $PPID)" =~ 'sshd' ]]
  • 检查 SSH 环境变量
  • 使用stat在/ dev /标准输入文件描述符
  • 通过检查交互模式 [[ "${-}" =~ 'i' ]]
  • 通过tty和检查 tty 状态tty -s
  • 检查ssh状态通过[[ "$(ps -o comm= -p $PPID)" =~ 'sshd' ]]

Note that if you are using an OS that supports the /procvirtual filesystem, you might have luck following the symbolic links for STDIO to determine whether a pipe is being used or not. However, /procis not a cross-platform, POSIX-compatible solution.

请注意,如果您使用的是支持/proc虚拟文件系统的操作系统,那么您可能会很幸运地遵循 STDIO 的符号链接来确定是否正在使用管道。但是,/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.

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

回答by Beano

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

回答by Dan Moulding

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

回答by sbj3

On Solaris, the suggestion from Dejay Clayton works mostly. The -p does not respond as desired.

在 Solaris 上,Dejay Clayton 的建议最有效。-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

:# 

回答by ATorras

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.

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

Hope it helps,

希望能帮助到你,