如何避免回显关闭 FIFO 命名管道? - Unix FIFO 的有趣行为

Posted

技术标签:

【中文标题】如何避免回显关闭 FIFO 命名管道? - Unix FIFO 的有趣行为【英文标题】:How to avoid echo closing FIFO named pipes? - Funny behavior of Unix FIFOs 【发布时间】:2012-01-14 16:14:07 【问题描述】:

我想将一些数据输出到管道,并让其他进程逐行对数据执行某些操作。这是一个玩具示例:

mkfifo pipe
cat pipe&
cat >pipe

现在我可以输入我想要的任何内容,按下回车后我会立即看到同一行。但是如果用echo代替第二个管道:

mkfifo pipe
cat pipe&
echo "some data" >pipe

管道在echocat pipe& 完成后关闭,因此我无法通过管道传递更多数据。有没有办法避免关闭管道和接收数据的进程,以便我可以从 bash 脚本通过管道传递多行数据并在它们到达时处理它们?

【问题讨论】:

【参考方案1】:

当打开 FIFO 进行读取时,它会阻塞调用进程(通常)。当一个进程打开 FIFO 进行写入时,读取器将被解除阻塞。当 writer 关闭 FIFO 时,读取过程会得到 EOF(要读取 0 个字节),除了关闭 FIFO 并重新打开之外,没有什么可以做的。因此,您需要使用循环:

mkfifo pipe
(while cat pipe; do : Nothing; done &)
echo "some data" > pipe
echo "more data" > pipe

另一种方法是在 FIFO 打开的情况下保持某些进程。

mkfifo pipe
sleep 10000 > pipe &
cat pipe &
echo "some data" > pipe
echo "more data" > pipe

【讨论】:

第二个版本做得很好!第一个对我不起作用,因为我不想重新启动接收数据的进程。 您可能可以作弊并让cat 保持管道打开,使用:cat pipe 3>pipecat 命令不会使用文件描述符 3,但会打开称为管道的 FIFO 进行写入(尽管它将在另一个文件描述符上读取它 - 可能是编号 4)。 exec >6 pipe 没有达到同样的效果吗?基本上将pipe 分配给文件描述符6 并将其保持打开以供写入。而不是直接写入pipe,您可能希望使用>&6 写入该描述符,否则它应该保持打开iirc @Haravikk:不,使用exec >6 pipe 将不起作用,即使将语法更正为exec 6> pipe。问题是进程会挂起,等待其他进程打开管道进行读取,而唯一计划这样做的进程是被阻塞的进程。 专业提示:使用tail -f 代替带有sleep 的版本。【参考方案2】:

将所有要输出到fifo的语句放在同一个子shell中:

# Create pipe and start reader.
mkfifo pipe
cat pipe &
# Write to pipe.
(
  echo one
  echo two
) >pipe

如果你有更复杂的,你可以打开管道写:

# Create pipe and start reader.
mkfifo pipe
cat pipe &
# Open pipe for writing.
exec 3>pipe
echo one >&3
echo two >&3
# Close pipe.
exec 3>&-

【讨论】:

优秀的答案,实际上成功地在任意复杂的命令序列中保持管道打开。 您能简单解释一下这是如何工作的吗?尤其是最后一行:exec 3>&- @NickChammas:在第二个示例中,exec 3>pipe 打开文件描述符 3 以写入 pipe;两个echo 命令通过输出重定向写入管道;最后一个exec 3>&- 是你如何关闭一个打开的文件描述符——在这种情况下是描述符3。此时,在后台运行的cat 会收到 EOF 并终止。【参考方案3】:

作为此处其他解决方案的替代方案,您可以在循环中调用 cat 作为命令的输入:

mkfifo pipe
(while true ; do cat pipe ; done) | bash

现在你可以一次给它一个命令,它不会关闭:

echo "'echo hi'" > pipe
echo "'echo bye'" > pipe

当然,当你想要它消失时,你必须终止它。我认为这是最方便的解决方案,因为它允许您在创建流程时指定非退出行为。

【讨论】:

【参考方案4】:

我从 Jonathan Leffler 的回答中增强了第二个版本以支持关闭管道:

dir=`mktemp -d /tmp/temp.XXX`
keep_pipe_open=$dir/keep_pipe_open
pipe=$dir/pipe

mkfifo $pipe
touch $keep_pipe_open

# Read from pipe:
cat < $pipe &

# Keep the pipe open:
while [ -f $keep_pipe_open ]; do sleep 1; done > $pipe &

# Write to pipe:
for i in 1..10; do
  echo $i > $pipe
done

# close the pipe:
rm $keep_pipe_open
wait

rm -rf $dir

【讨论】:

【参考方案5】:

您可以通过在读写模式下打开管道的读取端来轻松解决这个问题。读者只有在最后一个作者关闭后才能获得 EOF。因此,以读写方式打开它可以确保始终至少有一个写入器。

所以将第二个示例更改为:

mkfifo pipe
cat <>pipe &
echo "some data" >pipe

【讨论】:

使用这种方法我无法弄清楚如何关闭管道,我只能杀死可能行为不同的 cat 进程。例如,如果 cat 实际上是一个带有 END 块的 awk 程序,则在收到 SIGTERM 时不会执行 END 块。 @pix 您的用例与原始问题不太一样。但正如答案中提到的,“读者只有在最后一个作家关闭后才会得到一个EOF”,所以要确保总是有一个作家。例如exec 3&gt;pipe 让外壳保持打开状态。或者 sleep inf &gt;pipe &amp; 启动一个单独的进程,如果你希望它在 shell 退出后持续存在。 "只有在最后一个写入器关闭后,读取器才会收到 EOF。" - 但由于您的读者也是作家,因此“最后一个作家关闭”是您永远无法达到的条件,因为您的读者在获得只有它的退出才会产生的 EOF 之前不会退出。【参考方案6】:

老实说,我能够让它工作的最好方法是使用socat,它基本上连接了两个套接字。

mkfifo foo
socat $PWD/foo /dev/tty

现在换个新学期,你可以:

echo "I am in your term!" > foo
# also (surprisingly) this works
clear > foo

缺点是您需要 socat,这不是每个人都能获得的基本工具。好的一面是,我找不到不起作用的东西......我可以打印颜色,tee 到先进先出,清除屏幕等。就好像你从属于整个终端一样。

【讨论】:

谢谢!这是我见过的针对此问题的最简单、最有效且最不麻烦的解决方案!

以上是关于如何避免回显关闭 FIFO 命名管道? - Unix FIFO 的有趣行为的主要内容,如果未能解决你的问题,请参考以下文章

IPC - 命名管道(fifo)- 使用

IPC - 命名管道(fifo)- 使用

IPC - 命名管道(fifo)- 使用

IPC - 命名管道(fifo)- 使用

Bash 将 stdio 重定向到命名管道

简述Linux进程间通信之命名管道FIFO