Bash 协进程
bash 4.0 的新特性之一是coproc语句。这个coproc语句允许您创建一个协进程,该协进程通过两个管道连接到调用它的 shell:一个用于向协进程发送输入,另一个用于从协进程获取输出。
我发现的第一个用途是在尝试进行日志记录并使用 exec 重定向 时发现的。目标是允许您选择性地开始将脚本的所有输出写入日志文件,一旦脚本已经开始(例如,由于使用了 --log 命令行选项)。
在脚本已经开始后记录输出的主要问题是,脚本可能在调用时输出已经被重定向(到文件或管道)。如果我们改变输出的去向,当输出已经被重定向时,我们将不会按照用户的意图执行命令。
之前的 尝试最终使用了命名管道
#!/bin/bash
echo hello
if test -t 1; then
# Stdout is a terminal.
exec >log
else
# Stdout is not a terminal.
npipe=/tmp/$$.tmp
trap "rm -f $npipe" EXIT
mknod $npipe p
tee <$npipe log &
exec 1>&-
exec 1>$npipe
fi
echo goodbye
来自之前的文章
在这里,如果脚本的 stdout 没有连接到终端,我们使用 mknod 创建一个命名管道(一个存在于文件系统中的管道),并设置一个 trap 在退出时删除它。然后我们在后台启动 tee,从命名管道读取并写入日志文件。请记住,tee 也会将其 stdin 上读取的任何内容写入其 stdout。还要记住,tee 的 stdout 也与脚本的 stdout(我们的主脚本,即调用 tee 的脚本)相同,因此来自 tee 的 stdout 的输出将转到我们当前的 stdout 的任何位置(即,用户在命令行上指定的重定向或管道)。因此,此时我们让 tee 的输出转到它需要去的地方:进入用户指定的重定向/管道。
我们可以使用协进程做同样的事情
echo hello
if test -t 1; then
# Stdout is a terminal.
exec >log
else
# Stdout is not a terminal.
exec 7>&1
coproc tee log 1>&7
#echo Stdout of coproc: ${COPROC[0]} >&2
#echo Stdin of coproc: ${COPROC[1]} >&2
#ls -la /proc/$$/fd
exec 7>&-
exec 7>&${COPROC[1]}-
exec 1>&7-
eval "exec ${COPROC[0]}>&-"
#ls -la /proc/$$/fd
fi
echo goodbye
echo error >&2
如果我们的标准输出要输出到终端,那么我们就像以前一样,只使用 exec 将我们的输出重定向到所需的日志文件。如果我们的输出不输出到终端,那么我们使用 coproc 运行tee作为协进程,并将我们的输出重定向到 tee 的输入,并将 tee 的输出重定向到我们输出最初要去的地方。
使用 coproc 语句运行 tee 本质上与在后台运行 tee 相同(例如,tee log &),主要的区别在于 bash 运行 tee 时,其输入和输出都连接到管道。Bash 将这些管道的文件描述符放入一个名为COPROC(默认情况下)的数组中
- COPROC[0]是连接到协进程标准输出的管道的文件描述符
- COPROC[1]连接到协进程的标准输入。
请注意,这些管道是在命令中进行任何重定向之前创建的。
关注原始脚本的输出未连接到终端的部分。以下行将我们的标准输出复制到文件描述符 7。
exec 7>&1
然后我们启动 tee,将其输出重定向到文件描述符 7。
coproc tee log 1>&7
因此,tee 现在会将其标准输入上读取的任何内容写入名为log的文件和文件描述符 7,即我们原始的标准输出。
现在我们使用以下命令关闭文件描述符 7(请记住,tee 仍然将文件描述符 7 上打开的“文件”作为其标准输出打开):
exec 7>&-
由于我们已经关闭了 7,我们可以重用它,所以我们将连接到 tee 输入的管道移动到 7,使用
exec 7>&${COPROC[1]}-
然后我们通过以下命令将我们的标准输出移动到连接到 tee 标准输入的管道(我们的文件描述符 7):
exec 1>&7-
最后,我们关闭连接到 tee 输出的管道,因为我们不需要它,使用
eval "exec ${COPROC[0]}>&-"
这里的eval是必需的,因为否则 bash 会认为${COPROC[0]}的值是命令名称。另一方面,在上面的语句中(exec 7>&${COPROC[1]}-)不需要,因为在那条语句中 bash 可以识别出 "7" 是文件描述符操作的开始,而不是命令。
另请注意注释掉的命令
#ls -la /proc/$$/fd
这对于查看当前进程打开的文件很有用。
我们现在已经达到了预期的效果:我们的标准输出正在进入 tee。Tee 正在将其“记录”到我们的日志文件中,并将其写入我们输出最初要去的管道或文件。
到目前为止,我还没有想出协进程的其他用途,至少不是人为的用途。有关协进程的更多信息,请参阅 bash 手册页。