我想让一个进程从从多个源接收数据的命名管道中读取数据:
$ mkfifo /tmp/p
但我不知道如何让它持续工作。
第一个场景 - 这可行
终端1:
设置两个进程来写入我的fifo;这两个都会阻止:
$ echo 'first' > /tmp/p; echo 'second' > /tmp/p
终端2:
从管道中读取:
$ cat /tmp/p
first
second
如果我以相反的顺序执行上述操作,这仍然有效
当我想从管道中发出两个单独的命令时,我的问题就出现了:
第二种情况 - 不起作用
首先.sh
#!/bin/sh
echo 'first' > /tmp/p
第二个.sh
#!/bin/sh
echo 'second' > /tmp/p
终端1
$ sh first.sh; sh second.sh
终端2
$ cat /tmp/p
first
我的第一个 tty的执行sh second.sh
将无限期地阻塞,直到从命名管道读取其他内容。
我认为正在发生的事情
从http://linux.die.net/man/7/pipe:
如果引用管道写入端的所有文件描述符都已关闭,则尝试从管道读取(2)将看到文件结尾(读取(2)将返回0)
因此,当echo
退出时first.sh
,执行它的 shell 会关闭 的文件描述符/tmp/p
,这意味着cat
在我的第二个 TTY 中看到 EOF。
我如何使用 shell 解决这个问题?有没有办法在我的主控制脚本中保留对命名管道读取端的引用,以便在子 shell 退出时它不会被关闭?实际上,我会将命名管道的路径传递给子 shell。我是否需要将我的子 shell 输出到它们自己的标准输出并对其执行重定向?
我觉得这里缺少了一些东西。除了本例之外,使用命名管道对于我尝试做的所有事情来说都是简单明了的。
答案1
为什么不直接做:
{ echo foo; echo bar;} > /tmp/p
如果您希望控制脚本保持管道打开,您可以执行以下操作:
exec 3<> /tmp/p
以读写模式打开命名管道是为了避免在管道尚未打开时发生阻塞。如果还没有的话,它将实例化它。它至少可以在 Linux 上运行,但 POSIX 不保证它可以运行。
或者(并且可移植):
: < /tmp/p & exec 3> /tmp/p
然后,您也可以执行以下操作,而不是让每个进程都打开命名管道:
cmd >&3
最后,你会这样做:
exec 3>&-
结束写作,让读者知道它已经完成。
如果您需要相反的逻辑,请将所有<
s 更改为>
s,将<
s 更改为s。>
答案2
正如您所发现的,您无法可靠地使用 cat 从命名管道中读取数据。几年前我遇到了完全相同的问题,并写道PCAT来克服这个限制。
答案3
你只是误解了 cat 的工作原理。 echo 的退出也使 cat 退出。因此,如果您无法强制写入进程打开 rw 管道(并保持打开状态),那么您只需在循环中调用 cat 即可:
while true; do cat /tmp/p || break; done
当您使用例如 ^C 取消 cat 时,break 会变为活动状态。
答案4
可以手动保持对 fifo 的伪写入引用打开(使用tail -f /dev/null 1>fifo
或cat fifo 3>fifo
),以便 fifo 的读取端不会在第一次读取后关闭。
# tty1
bash -c '
mkfifo fifo || exit 1
tail -f /dev/null 1>fifo &
#0<&- cat fifo 3>fifo &
#lsof -p $!
echo first > fifo
echo second > fifo
kill $!
'
# tty2
cat fifo