Bash 将所有输出重定向到命名管道

Bash redirect all output to named pipes

我一直在寻找一种方法来使用 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

这段代码似乎没问题,但终端会无限期挂起,直到它终止。据我了解,管道就像文件一样,因为它们有一个索引节点,但它们不是写入磁盘,而是简单地写入内存。

也就是说,它应该像任何其他文件一样可以访问。我知道脚本挂在调用打印机函数的行上。我还测试了几种子外壳和更高级重定向的组合(即重定向到 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 >"${pipe1}" 2>"${pipe2} 语句执行,代码就会挂起。我想在某些地方添加更多子 shell 会有所帮助,但这可能会变成 messy/unwieldy。然而,我确实了解到命名管道是为了在 shell 之间桥接数据(因此添加了子 shell 和后台运算符 &)。

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

在这样的设置中:

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}"

On cat < "$pipe1" 因为您需要从两个管道读取才能让 exec 继续:

(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

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