如何在两个程序之间制作双向管道?

70 shell pipe

每个人都知道如何在两个程序之间制作单向管道(stdout第一个和stdin第二个程序的绑定):first | second.

但如何建立双向管道,即交叉结合stdinstdout的两个方案?有没有简单的方法可以在 shell 中做到这一点?

der*_*ert 45

好吧,使用命名管道 ( mkfifo)相当“容易” 。我把容易放在引号中,因为除非程序是为此而设计的,否则很可能会出现死锁。

mkfifo fifo0 fifo1
( prog1 > fifo0 < fifo1 ) &
( prog2 > fifo1 < fifo0 ) &
( exec 30<fifo0 31<fifo1 )      # write can't open until there is a reader
                                # and vice versa if we did it the other way
Run Code Online (Sandbox Code Playgroud)

现在,写入标准输出通常涉及缓冲。因此,例如,如果两个程序都是:

#!/usr/bin/perl
use 5.010;
say 1;
print while (<>);
Run Code Online (Sandbox Code Playgroud)

你会期待一个无限循环。但相反,两者都会陷入僵局;您需要添加$| = 1(或等效)以关闭输出缓冲。造成死锁的原因是两个程序都在等待 stdin 上的某些内容,但他们没有看到它,因为它位于另一个程序的 stdout 缓冲区中,并且尚未写入管道。

更新:结合 Stéphane Charzelas 和 Joost 的建议:

mkfifo fifo0 fifo1
prog1 > fifo0 < fifo1 &
prog2 < fifo0 > fifo1
Run Code Online (Sandbox Code Playgroud)

做同样的,更短,更便携。

  • 一个命名管道就足够了:`prog1 &lt; fifo | prog2 &gt; fifo`。 (28认同)
  • 如果你使它成为 `prog2 &lt; fifo0 &gt; fifo1`,你可以避免你用 `exec 30&lt; ...` 的小舞(顺便说一下,它只适用于 `bash` 或 `yash` 超过 10 的 fds)。 (4认同)
  • @AndreyVihrov 是的,您可以用匿名管道代替其中一个命名管道。但我喜欢对称:-P (3认同)
  • @user14284:在 Linux 上,您可能可以使用类似 `prog1 &lt; fifo | 三通/dev/stderr | 程序2 | tee /dev/stderr &gt; fifo`。 (3认同)

Sté*_*las 32

如果系统上的管道是双向的(至少在 Solaris 11 和某些 BSD 上是这样,但在 Linux 上不是这样):

cmd1 <&1 | cmd2 >&0
Run Code Online (Sandbox Code Playgroud)

不过要小心死锁。

另请注意,某些系统上的某些版本的 ksh93|使用套接字对实现管道 ( ) 。套接字对是双向的,但 ksh93 明确地关闭了相反的方向,因此即使在管道(由pipe(2)系统调用创建)是双向的系统上,上述命令也不适用于那些 ksh93 。

  • @heinrich5991 - [*“在某些系统(但不是 Linux)上,管道是双向的*”](http://www.kernel.org/doc/man-pages/online/pages/man7/pipe.7.html) (3认同)

jfg*_*956 13

我不确定这是否是您想要做的:

nc -l -p 8096 -c second &
nc -c first 127.0.0.1 8096 &
Run Code Online (Sandbox Code Playgroud)

这首先在端口 8096 上打开一个侦听套接字,一旦建立连接,就会生成程序second,将其stdin作为流输出和stdout流输入。

然后,nc启动第二个连接到侦听端口并生成程序first,将其stdout作为流输入并stdin作为流输出。

这不是完全使用管道完成的,但它似乎可以满足您的需求。

由于这使用网络,因此可以在 2 台远程计算机上完成。这几乎就是网络服务器 ( second) 和网络浏览器 ( first) 的工作方式。


小智 11

您可以使用pipexec

$ pipexec -- '[A' cmd1 ] '[B' cmd2 ] '{A:1>B:0}' '{B:1>A:0}'
Run Code Online (Sandbox Code Playgroud)


Any*_*Dev 7

bash版本 4 具有coproc允许在bash没有命名管道的情况下以纯方式完成此操作的命令:

coproc cmd1
eval "exec cmd2 <&${COPROC[0]} >&${COPROC[1]}"
Run Code Online (Sandbox Code Playgroud)

其他一些 shell 也可以这样做coproc

下面是更详细的答案,但链接了三个命令,而不是两个,这只是更有趣一点。

如果你也乐于使用catstdbuf那么construct 可以变得更容易理解。

使用版本bashcatstdbuf,容易理解:

# start pipeline
coproc {
    cmd1 | cmd2 | cmd3
}
# create command to reconnect STDOUT `cmd3` to STDIN of `cmd1`
endcmd="exec stdbuf -i0 -o0 /bin/cat <&${COPROC[0]} >&${COPROC[1]}"
# eval the command.
eval "${endcmd}"
Run Code Online (Sandbox Code Playgroud)

注意,必须使用 eval,因为 <&$var 中的变量扩展在我的 bash 4.2.25 版本中是非法的。

使用 pure 的版本bash:分成两部分,在 coproc 下启动第一个管道,然后午餐第二部分,(单个命令或管道)将其重新连接到第一个:

coproc {
    cmd 1 | cmd2
}
endcmd="exec cmd3 <&${COPROC[0]} >&${COPROC[1]}"
eval "${endcmd}"
Run Code Online (Sandbox Code Playgroud)

概念证明:

file ./prog,只是一个虚拟的 prog 来消费、标记和重新打印行。使用子shell 来避免缓冲问题可能有点矫枉过正,这不是重点。

#!/bin/bash
let c=0
sleep 2

[ "$1" == "1" ] && ( echo start )

while : ; do
  line=$( head -1 )
  echo "$1:${c} ${line}" 1>&2
  sleep 2
  ( echo "$1:${c} ${line}" )
  let c++
  [ $c -eq 3 ] && exit
done
Run Code Online (Sandbox Code Playgroud)

文件./start_cat 这是一个使用bash,catstdbuf

#!/bin/bash

echo starting first cmd>&2

coproc {
  stdbuf -i0 -o0 ./prog 1 \
    | stdbuf -i0 -o0 ./prog 2 \
    | stdbuf -i0 -o0 ./prog 3
}

echo "Delaying remainer" 1>&2
sleep 5
cmd="exec stdbuf -i0 -o0 /bin/cat <&${COPROC[0]} >&${COPROC[1]}"

echo "Running: ${cmd}" >&2
eval "${cmd}"
Run Code Online (Sandbox Code Playgroud)

或文件./start_part。这是一个bash只使用 pure 的版本。出于演示目的,我仍在使用,stdbuf因为您的真正编将不得不在内部处理缓冲,以避免因缓冲而阻塞。

#!/bin/bash

echo starting first cmd>&2

coproc {
  stdbuf -i0 -o0 ./prog 1 \
    | stdbuf -i0 -o0 ./prog 2
}

echo "Delaying remainer" 1>&2
sleep 5
cmd="exec stdbuf -i0 -o0 ./prog 3 <&${COPROC[0]} >&${COPROC[1]}"

echo "Running: ${cmd}" >&2
eval "${cmd}"
Run Code Online (Sandbox Code Playgroud)

输出:

> ~/iolooptest$ ./start_part
starting first cmd
Delaying remainer
2:0 start
Running: exec stdbuf -i0 -o0 ./prog 3 <&63 >&60
3:0 2:0 start
1:0 3:0 2:0 start
2:1 1:0 3:0 2:0 start
3:1 2:1 1:0 3:0 2:0 start
1:1 3:1 2:1 1:0 3:0 2:0 start
2:2 1:1 3:1 2:1 1:0 3:0 2:0 start
3:2 2:2 1:1 3:1 2:1 1:0 3:0 2:0 start
1:2 3:2 2:2 1:1 3:1 2:1 1:0 3:0 2:0 start
Run Code Online (Sandbox Code Playgroud)

这样做。


小智 6

编写这种双向管道的一个方便的构建块是将当前进程的 stdout 和 stdin 连接在一起的东西。我们称之为ioloop。调用这个函数后,你只需要启动一个普通的管道:

ioloop &&     # stdout -> stdin 
cmd1 | cmd2   # stdin -> cmd1 -> cmd2 -> stdout (-> back to stdin)
Run Code Online (Sandbox Code Playgroud)

如果您不想修改顶级 shell 的描述符,请在子 shell 中运行:

( ioloop && cmd1 | cmd2 )
Run Code Online (Sandbox Code Playgroud)

这是使用命名管道的 ioloop 的可移植实现:

ioloop() {
    FIFO=$(mktemp -u /tmp/ioloop_$$_XXXXXX ) &&
    trap "rm -f $FIFO" EXIT &&
    mkfifo $FIFO &&
    ( : <$FIFO & ) &&    # avoid deadlock on opening pipe
    exec >$FIFO <$FIFO
}
Run Code Online (Sandbox Code Playgroud)

命名管道仅在 ioloop 设置期间短暂存在于文件系统中。这个函数不是很 POSIX,因为 mktemp 已被弃用(并且可能容易受到竞争攻击)。

使用 /proc/ 的特定于 linux 的实现是可能的,它不需要命名管道,但我认为这个已经足够了。


Ale*_*ies 5

还有

  • dpipe,“双向管道”,包含在 vde2 包中,并包含在当前发行版包管理系统中

    dpipe processA = processB

  • socat,连接一切到一切的工具。

    socat EXEC:Program1 EXEC:Program2

正如@StéphaneChazelas 在评论中正确指出的那样,上面的例子是“基本形式”,他有很好的例子,他对类似问题的回答提供了选项