bash exec 将输出发送到管道,如何?

bash exec sending output to a pipe, how?

提问人:Harald 提问时间:8/15/2014 更新时间:11/18/2017 访问量:12409

问:

我尝试执行 bash 本身只是为了重定向输出。如果我使用重定向,比如

exec >bla.log
ls
exec 1>&2

它按预期工作:输出最终进入并在第二件事恢复正常之后,主要是因为句柄 2 仍然绑定到终端。lsbla.logexec

现在我想通过管道而不是文件发送输出,一个简单的例子是 .但是,该命令会立即返回。为了弄清楚发生了什么,我这样做了:exec | cat >bla.log

exec | bash -c 'echo $$; ls -l /proc/$$/fd /proc/23084/fd'

其中 23084 是当前正在运行的 bash 并得到这个:

24002
/proc/23084/fd:
total 0
lrwx------ 1 harald harald 64 Aug 14 20:17 0 -> /dev/pts/1
lrwx------ 1 harald harald 64 Aug 14 20:17 1 -> /dev/pts/1
lrwx------ 1 harald harald 64 Aug 14 20:17 2 -> /dev/pts/1
lrwx------ 1 harald harald 64 Aug 14 20:17 255 -> /dev/pts/1

/proc/24002/fd:
total 0
lr-x------ 1 harald harald 64 Aug 14 21:56 0 -> pipe:[58814]
lrwx------ 1 harald harald 64 Aug 14 21:56 1 -> /dev/pts/1
lrwx------ 1 harald harald 64 Aug 14 21:56 2 -> /dev/pts/1

正如我们所看到的,子进程 24002 确实在监听管道。但它肯定不是父进程 23084,它打开了这个管道。

知道这是怎么回事吗?

bash io-重定向

评论

1赞 Wrikken 8/15/2014
可能的解决方法
3赞 glenn jackman 8/15/2014
exec 1>&2并不完全是“恢复正常”——将 stdout 定向到 stderr。恢复正常会是这样的exec 3>&1; exec 1>blah.log; ls; exec 1>&3; exec 3>&-

答:

8赞 Barmar 8/15/2014 #1

当命令包含流水线时,每个子命令都在子 shell 中运行。因此,shell 首先为管道的每个部分分叉一个子 shell,然后第一个部分的子 shell 在没有参数的情况下执行,这不执行任何操作并退出。exec

exec具有重定向功能,并且没有命令被视为特殊情况。从文档中:

如果未指定命令,则任何重定向都会在 当前 shell,返回状态为 0。

27赞 Charles Duffy 8/15/2014 #2

什么

实现本来可能编写的内容的正确方法

exec | cat >bla.log

#!/bin/bash
#      ^^^^ - IMPORTANT: not /bin/sh

exec > >(cat >bla.log)

为什么

这是因为是进程替换;它被替换为文件名(如果可能,则为格式,否则为临时 FIFO),当写入时,该文件名将传递到封闭进程的 stdin。(是类似的,但在另一个方向上:被替换为类似文件对象的名称,该对象在读取时将返回给定进程的 stdout)。>()/dev/fd/NN<()

因此,大致等同于以下内容(在不提供 、 或类似功能的操作系统上):exec > >(cat >bla.log)/dev/fd/proc/self/fds

mkfifo "tempfifo.$$"           # implicit: FIFO creation
cat >bla.log <"tempfifo.$$" &  # ...start the desired process within it...
exec >"tempfifo.$$"            # explicit: redirect to the FIFO
rm "tempfifo.$$"               # ...and can unlink it immediately.

评论

0赞 Hubert Grzeskowiak 8/11/2017
有人可以解释一下吗?
1赞 Charles Duffy 8/11/2017
@HubertGrzeskowiak,修正。
0赞 akhan 7/20/2019
很有帮助。一个更有启发性的用例是使用该实用程序对所有输出进行时间戳。ts
0赞 stimur 12/1/2020
我认为这是一种方法,应该被标记为解决方案,另一个只是解释为什么它不起作用。
0赞 stimur 12/1/2020
我最终做了这样的事情: ''' #!/bin/bash exec > >(logger -t bashtest) 2>&1 ls -l ls -lqwe12 '''
5赞 JinnKo 11/18/2017 #3

我花了一段时间才弄清楚如何让 stdout 和 stderr 的重定向组合正确,所以这可能对其他人有用。

以下示例演示了在区分 stdout 和 stderr 时将 tee 用作“管道”目标的重定向。

#!/bin/bash

echo "stdout"
echo "stderr" >&2

echo "stdout to out.log" | tee out.log
echo "stderr to err.log" 2>&1 >&2 | tee err.log >&2

exec 2> >(tee -a err.log >&2)
exec > >(tee -a out.log)

echo "exec stdout to out.log"
echo "exec stderr to err.log" >&2

在 CLI 中运行此命令,并将 stdout 重定向到 /dev/null,您只能看到 stderr 消息。此外,在每个日志文件中,您只能看到相关消息。

请注意,线条和线条的顺序很重要。从本质上讲,我们首先要将 stderr 重定向到错误文件,然后将 stdout 重定向到日志文件。如果这些行以相反的顺序出现,则结果将不正确。exec 2>exec >

评论

2赞 Charles Duffy 12/9/2017
顺便说一句,您可以只使用一个具有多个重定向的重定向;它们将按从左到右的顺序执行。因此,作为单个命令工作。execexec 2> >(tee -a err.log >&2) > >(tee -a out.log)
0赞 Charles Duffy 12/9/2017
(顺便说一句,这绝对是一个有用的答案,也是对网站的有用贡献,但我不确定这个问题是否适合它——这里的 OP 不是问如何将 stdout 和 stderr 复制到单独的文件,而是问如何将它们定向到同一个目的地;这看起来更像是当前问题与 Bash 重定向的混合体: 将 stdout/stderr 保存到不同的文件,并仍然在控制台上打印出来)。