将两个命令传输到命名管道时发生竞争

将两个命令传输到命名管道时发生竞争

我想让一个进程从从多个源接收数据的命名管道中读取数据:

$ 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>fifocat 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

相关内容