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

Cer*_*era 5 bash pipe

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

$ mkfifo /tmp/p
Run Code Online (Sandbox Code Playgroud)

但我不知道如何让它始终如一地工作。

第一个场景 - 这有效

tty1 :

设置两个进程写入我的fifo;这两个都会阻止:

$ echo 'first' > /tmp/p; echo 'second' > /tmp/p
Run Code Online (Sandbox Code Playgroud)

tty2 :

从管道中读取:

$ cat /tmp/p
first
second
Run Code Online (Sandbox Code Playgroud)

如果我以相反的顺序执行上述操作,这仍然有效

当我有两个单独的命令想要从管道中出来时,我的问题就出现了:

第二个场景 - 不起作用

第一个.sh

#!/bin/sh
echo 'first' > /tmp/p
Run Code Online (Sandbox Code Playgroud)

第二个.sh

#!/bin/sh
echo 'second' > /tmp/p
Run Code Online (Sandbox Code Playgroud)

tty1

$ sh first.sh; sh second.sh
Run Code Online (Sandbox Code Playgroud)

tty2

$ cat /tmp/p
first
Run Code Online (Sandbox Code Playgroud)

sh second.sh从我的第一个 tty执行将无限期地阻塞,直到从命名管道中读取其他内容。

我认为正在发生的事情

来自http://linux.die.net/man/7/pipe

如果所有引用管道写端的文件描述符都已关闭,则尝试从管道读取(2)将看到文件结束(读取(2)将返回 0)

因此,当echo退出 in 时first.sh,执行它的 shell 会关闭 的文件描述符/tmp/p,这意味着cat在我的第二个 TTY 中看到 EOF。

我如何用外壳解决这个问题?有没有办法在我的主控制脚本中保留对命名管道读取端的引用,以便在子外壳退出时它不会关闭?在实践中,我会将命名管道的路径传递给子外壳。我是否只需要让我的子 shell 输出到它们自己的标准输出并对其执行重定向?

我觉得这里缺少一些东西。除了这种情况外,对于我尝试做的所有事情,使用命名管道都很简单明了。

Sté*_*las 7

为什么不这样做:

{ echo foo; echo bar;} > /tmp/p
Run Code Online (Sandbox Code Playgroud)

如果您希望您的控制脚本让管道保持打开状态,您可以执行以下操作:

exec 3<> /tmp/p
Run Code Online (Sandbox Code Playgroud)

以读写模式打开命名管道是为了避免在管道尚未打开时阻塞。如果它还没有,那将实例化它。它至少适用于 Linux,但 POSIX 不保证。

或者(并且可移植):

: < /tmp/p & exec 3> /tmp/p
Run Code Online (Sandbox Code Playgroud)

然后,您还可以执行以下操作,而不是让每个进程打开命名管道:

cmd >&3
Run Code Online (Sandbox Code Playgroud)

最后,你会这样做:

exec 3>&-
Run Code Online (Sandbox Code Playgroud)

结束写作,让读者知道它已经完成了。

如果您需要相反的逻辑,请将所有<s更改为>s 并将<s更改为s >


Den*_*ker 2

正如您所发现的,您无法可靠地使用 cat 从命名管道中读取数据。几年前我遇到了完全相同的问题,并编写了pcat来克服这个限制。

  • 这与这里的“cat”无关,你的“pcat”可以写成“cat p 3&gt; p”并且永远不会返回。 (2认同)