【问题标题】:Bash redirect all output to named pipesBash 将所有输出重定向到命名管道
【发布时间】:2019-02-10 01:43:38
【问题描述】:

我一直在寻找一种方法来使用 Bash 间接将所有输出(1 (STDOUT)、2 (STDERR)、3 等)重新路由到命名管道。这是我为测试该理论而编写的脚本:

#!/bin/bash

pipe1="/tmp/pipe1"
pipe2="/tmp/pipe2"
pipe3="/tmp/pipe3"

mkfifo "${pipe1}"
mkfifo "${pipe2}"
mkfifo "${pipe3}"

trap "rm -rf ${pipe1} ${pipe2} ${pipe3}" EXIT

printer() {
  echo "OUT" >&1
  echo "ERR" >&2
  echo "WRN" >&3
}

# Usage: mux
mux() {
  cat "${pipe1}"
  cat "${pipe2}"
  cat "${pipe3}"
}

printer 1>"${pipe1}" 2>"${pipe2}" 3>"${pipe3}"
mux

这段代码似乎没问题,但终端无限期挂起,直到它被终止。据我了解,管道就像文件一样,它们有一个 inode,但它们不是写入磁盘,而是写入内存。

话虽如此,它应该像任何其他文件一样可以访问。我知道脚本挂在调用打印机函数的行上。我还测试了几种子shell 组合和更高级的重定向(即重定向到STDOUT 以处理其他每个管道)。也许我在命名管道中缺少一个终止符(因此它被锁定并且不能被 mux 函数访问)。如果是这样,这是如何实现的?

编辑 经过更多测试后,该问题似乎仅在尝试使用多个管道重定向时发生。例如:

#!/bin/bash

pipe1="/tmp/pipe1"
mkfifo "${pipe1}"
trap "rm -rf ${pipe1}" EXIT

(exec >"${pipe1}"; echo "Test") &
cat < "${pipe1}"

将按预期工作。但是,添加 STDOUT(例如)会破坏它,迫使它挂起:

#!/bin/bash

pipe1="/tmp/pipe1"
mkfifo "${pipe1}"
trap "rm -rf ${pipe1}" EXIT

(exec >"${pipe1}" 2>"${pipe2}"; echo "Test"; echo "Test2" >&2) &
cat < "${pipe1}"
cat < "${pipe2}"

更具体地说,一旦exec &gt;"${pipe1}" 2&gt;"${pipe2} 语句执行,代码就会挂起。我想在某些地方添加更多子shell会有所帮助,但这可能会变得混乱/笨拙。但是,我确实了解到,命名管道是为了在 shell 之间桥接数据(因此添加了子 shell 和后台运算符 &amp;)。

【问题讨论】:

  • 为了能够写入命名管道,必须同时打开管道进行读取。否则写入进程将阻塞,直到进程打开管道进行读取。管道的两端必须可用
  • @oguzismail 这很有道理。我找到了this site,它在文章的中途解释了这个原则。当然,没有两端的管道只会泄漏:)。但是,在向管道添加信息之前,我应该调用cat 命令(或等效的输入阅读器)吗?我不太确定如何在不孤立进程的情况下同时读写(这也不起作用)。
  • 其实我今天想了一些你的问题,但没有想出解决方案,对不起:/
  • 没关系。我很欣赏你的想法。我进行了更多测试并缩小了问题范围。我将在上面添加一个编辑。

标签: bash pipe stdout named-pipes indirection


【解决方案1】:

如果你想在文件描述符关闭后能够读取内容,你只需要使用文件即可。管道的想法是,读取命令需要在写入命令之前先运行。

在这样的设置中:

cmd1 | cmd2 | cmd3

首先运行cmd3,然后是cmd2,然后是cmd1。所以如果你想使用管道设置它,你需要打开每个fifo并行读取,然后调用printer

printer() {
  echo "OUT" >&1
  echo "ERR" >&2
  echo "WRN" >&3
}

# Usage: mux
mux() {
  cat "${pipe1}" &
  cat "${pipe2}" &
  cat "${pipe3}"
}

mux &
printer 1>${pipe1} 2>"${pipe2}" 3>"${pipe3}"

shell 会在这个片段上阻塞:

(exec >"${pipe1}" 2>"${pipe2}"; echo "Test"; echo "Test2" >&2) &
cat < "${pipe1}"
cat < "${pipe2}"

cat &lt; "$pipe1" 上,因为您需要从两个管道中读取数据才能继续执行:

(exec >"${pipe1}" 2>"${pipe2}"; echo "Test"; echo "Test2" >&2) &
cat < "${pipe1}" &
cat < "${pipe2}"

如果你想要一个命令的缓冲输出,即。在命令写入或退出后读取命令的输出,仅使用文件,它们实际上称为日志。

作为一种解决方法,您可以使用 bash 管道内部缓冲来缓冲您的消息:

printer() {
  echo "OUT" >&3
  echo "ERR" >&4
  echo "WRN" >&5
}

# Usage: mux
mux() {
  timeout 1 cat "${pipe1}"
  timeout 1 cat "${pipe2}"
  timeout 1 cat "${pipe3}"
}

printer 3> >(cat >$pipe1) 4> >(cat >$pipe2) 5> >(cat >$pipe3)
mux

这里发生的情况是,管道始终为写入打开,即使在打印机功能存在之后也会保持打开状态,直到进程替换运行。您可以通过exec 5&gt;&amp;- 手动关闭它们,这会将EOF 写入管道,让cat $pipe3 正常返回。如果函数不关闭文件描述符,cat "$pipe1" 将永远不会退出,这就是使用超时函数的原因,这样我们就可以在不阻塞管道的情况下排出管道。

【讨论】:

  • 有趣。我有点害怕推理。似乎命名管道可能不适合我的用例。话虽如此,我可能会尝试另一条路线,至少使用文件/日志。至于重定向到 fd 的 3、4 和 5:这将违背我的目标,但是,我承认这是一个很好的解决方法(我必须假设我的函数在 fd3 上输出 STDOUT、STDERR 和警告) .无论如何,我在这方面学到了很多。谢谢您的帮助! :)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-09-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-12-21
  • 1970-01-01
相关资源
最近更新 更多