如何从 shell 脚本中检测其标准输出是被发送到终端还是被管道发送到另一个进程?
具体情况是: 我想添加转义码以着色输出,但仅在交互式运行时添加,而不在管道发送时添加,类似于ls --color
所做的。
如何从 shell 脚本中检测其标准输出是被发送到终端还是被管道发送到另一个进程?
具体情况是: 我想添加转义码以着色输出,但仅在交互式运行时添加,而不在管道发送时添加,类似于ls --color
所做的。
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
标志在 man 页面中描述为:
-t fd 如果文件描述符 fd 打开并且引用终端,则为True。
... 其中 fd
可以是通常的文件描述符分配之一:
-t
标志在 POSIX 中被指定,因此应该适用于任何符合 POSIX 标准的 shell(也就是说,它不是 bash 的扩展)。参考链接:http://pubs.opengroup.org/onlinepubs/009695399/utilities/test.html - FireFly0
、1
和2
都会被默认分配。 - Shuzheng没有绝对可靠的方法来确定 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 中区分。
当您想编写一个具有类似编译实用程序(如 cat
)的行为的 bash 脚本时,通常会遇到此限制。例如,cat
允许同时处理各种输入源的灵活行为,并且足够聪明,可以确定是否收到管道输入,无论是使用非 TTY 还是强制 TTY 的 ssh
。
ssh -t localhost 'echo piped | cat - <( echo substituted )'
ssh -T localhost 'echo piped | cat - <( echo substituted )'
ssh -t
和ssh -T
,你会发现那些使用ssh -t
可行的方法,在使用ssh -T
时并不起作用。 - Dejay Claytonssh -[tT]
、bash -c
还是直接运行,都没有产生任何明显的输出差异。而且我在 man cat
中也没有看到任何关于 TTY 的说明。 - usretchelp test
(以及更多的help help
),然后查看info bash
以获取更深入的信息。如果您需要脱机编写脚本或只是想获得更广泛的理解,这些命令非常有用。 - Joel Purra您没有提及使用哪种shell,但在Bash中,您可以这样做:
#!/bin/bash
if [[ -t 1 ]]; then
# stdout is a terminal
else
# stdout is not a terminal
fi
-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'
:$ ./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
:# ./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
:#
ls /proc/$$/fdinfo/* >/dev/null 2>&1 || grep -q 'flags: 00$' /proc/$$/fdinfo/0 && echo "pipe detected"
我不知道为什么,但似乎当Bash函数被标准输入管道时,文件描述符“3”会被创建。