每个人都知道如何在两个程序之间制作单向管道(stdout第一个和stdin第二个程序的绑定):first | second.
但如何建立双向管道,即交叉结合stdin和stdout的两个方案?有没有简单的方法可以在 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)
做同样的,更短,更便携。
Sté*_*las 32
如果系统上的管道是双向的(至少在 Solaris 11 和某些 BSD 上是这样,但在 Linux 上不是这样):
cmd1 <&1 | cmd2 >&0
Run Code Online (Sandbox Code Playgroud)
不过要小心死锁。
另请注意,某些系统上的某些版本的 ksh93|使用套接字对实现管道 ( ) 。套接字对是双向的,但 ksh93 明确地关闭了相反的方向,因此即使在管道(由pipe(2)系统调用创建)是双向的系统上,上述命令也不适用于那些 ksh93 。
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)
bash版本 4 具有coproc允许在bash没有命名管道的情况下以纯方式完成此操作的命令:
coproc cmd1
eval "exec cmd2 <&${COPROC[0]} >&${COPROC[1]}"
Run Code Online (Sandbox Code Playgroud)
其他一些 shell 也可以这样做coproc。
下面是更详细的答案,但链接了三个命令,而不是两个,这只是更有趣一点。
如果你也乐于使用cat,stdbuf那么construct 可以变得更容易理解。
使用版本bash与cat和stdbuf,容易理解:
# 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,cat和stdbuf
#!/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 的实现是可能的,它不需要命名管道,但我认为这个已经足够了。