每个人都知道如何使两个程序(绑定之间的单向管stdout
第一个和stdin
第二之一): first | second
。
但是如何制作双向管道,即交叉绑定stdin
和stdout
两个程序?有没有简单的方法可以在Shell中完成?
每个人都知道如何使两个程序(绑定之间的单向管stdout
第一个和stdin
第二之一): first | second
。
但是如何制作双向管道,即交叉绑定stdin
和stdout
两个程序?有没有简单的方法可以在Shell中完成?
Answers:
如果系统上的管道是双向的(至少在Solaris 11和某些BSD上是这样,但在Linux上不是):
cmd1 <&1 | cmd2 >&0
不过要当心死锁。
另请注意,某些系统上的某些版本的ksh93 |
使用套接字对实现管道()。套接字对是双向的,但是ksh93明确地关闭了反向连接,因此,即使在管道(由pipe(2)
系统调用创建)是双向的系统上,上述命令也不适用于那些ksh93 。
好吧,使用命名管道(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
现在,通常在编写标准输出时涉及缓冲。因此,例如,如果两个程序都是:
#!/usr/bin/perl
use 5.010;
say 1;
print while (<>);
您会期待一个无限循环。但是相反,两者都会陷入僵局。您需要添加$| = 1
(或等效功能)以关闭输出缓冲。造成死锁的原因是,两个程序都在stdin上等待,但由于它位于另一个程序的stdout缓冲区中而尚未写入管道,因此他们没有看到它。
更新:结合了StéphaneCharzelas和Joost的建议:
mkfifo fifo0 fifo1
prog1 > fifo0 < fifo1 &
prog2 < fifo0 > fifo1
相同,更短,更便于携带。
prog1 < fifo | prog2 > fifo
。
prog1 < fifo | tee /dev/stderr | prog2 | tee /dev/stderr > fifo
。
prog2 < fifo0 > fifo1
,则可以避免与之跳舞exec 30< ...
(顺便说一句,它只能与10个bash
或以上yash
的fds 一起使用)。
dash
似乎也不错(但行为有所不同)
我不确定这是否是您要尝试的操作:
nc -l -p 8096 -c second &
nc -c first 127.0.0.1 8096 &
首先,打开端口8096上的侦听套接字,一旦建立连接,就将second
其stdin
作为流输出和stdout
流输入生成程序。
然后,nc
启动第二个程序first
,该程序连接到侦听端口,并以其stdout
作为流输入并以其stdin
作为流输出生成程序。
使用管道并不能完全做到这一点,但是它似乎可以满足您的需求。
由于它使用网络,因此可以在两台远程计算机上完成。这几乎是网络服务器(second
)和网络浏览器(first
)的工作方式。
nc -U
对于UNIX域套接字,只有采取文件系统的地址空间。
您可以使用pipexec:
$ pipexec -- '[A' cmd1 ] '[B' cmd2 ] '{A:1>B:0}' '{B:1>A:0}'
bash
版本4的coproc
命令允许在bash
不使用命名管道的情况下完成此操作:
coproc cmd1
eval "exec cmd2 <&${COPROC[0]} >&${COPROC[1]}"
其他一些壳也可以做coproc
。
下面是更详细的答案,但链接了三个命令,而不是两个,这使命令变得更加有趣。
如果您也乐于使用cat
,stdbuf
则可以使构造更易于理解。
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}"
注意,必须使用eval,因为在我的bash 4.2.25版本中,<&$ var中的变量扩展是非法的。
使用pure的版本bash
:分为两部分,在coproc下启动第一个管道,然后在第二部分进餐,(将单个命令或管道)重新连接到第一部分:
coproc {
cmd 1 | cmd2
}
endcmd="exec cmd3 <&${COPROC[0]} >&${COPROC[1]}"
eval "${endcmd}"
概念证明:
文件./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
文件./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}"
或文件./start_part
。这是bash
仅使用纯的版本。出于演示目的,我仍在使用,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}"
输出:
> ~/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
做到了。
编写此类双向管道的便捷构建模块是将当前进程的stdout和stdin连接在一起的工具。让我们称之为ioloop。调用此函数后,只需要启动常规管道即可:
ioloop && # stdout -> stdin
cmd1 | cmd2 # stdin -> cmd1 -> cmd2 -> stdout (-> back to stdin)
如果您不想修改顶级外壳程序的描述符,请在子外壳程序中运行它:
( ioloop && cmd1 | cmd2 )
这是使用命名管道的ioloop的可移植实现:
ioloop() {
FIFO=$(mktemp -u /tmp/ioloop_$$_XXXXXX ) &&
trap "rm -f $FIFO" EXIT &&
mkfifo $FIFO &&
( : <$FIFO & ) && # avoid deadlock on opening pipe
exec >$FIFO <$FIFO
}
命名管道仅在ioloop设置期间短暂存在于文件系统中。此功能不是POSIX,因为不建议使用mktemp(并且可能容易受到种族攻击)。
使用/ proc /的特定于Linux的实现是可能的,不需要命名管道,但是我认为这已经足够了。
( : <$FIFO & )
更详细的解释。感谢您的发表。
mktemp
?我已经广泛使用它,如果已经有了更新的工具,我想开始使用它。
也有
dpipe
,“双向管道”,包含在vde2软件包中,并包含在当前的发行版软件包管理系统中。
dpipe processA = processB
socat,即万物连接工具。
socat EXEC:Program1 EXEC:Program2
正如@StéphaneChazelas在注释中正确指出的那样,以上示例是“基本形式”,他有不错的示例,并在选项中回答了类似的问题。
socat
使用套接字而不是管道(可以使用进行更改commtype=pipes
)。您可能希望添加该nofork
选项,以避免额外的socat进程在管道/插座之间填充数据。(感谢在编辑我的答案 BTW)
这里有很多很棒的答案。因此,我只想添加一些内容以方便与他们一起玩耍。我认为stderr
没有重定向到任何地方。创建两个脚本(假设a.sh和b.sh):
#!/bin/bash
echo "foo" # change to 'bar' in second file
for i in {1..10}; do
read input
echo ${input}
echo ${i} ${0} got: ${input} >&2
done
然后,当您以任何好的方式连接它们时,您应该在控制台上看到:
1 ./a.sh got: bar
1 ./b.sh got: foo
2 ./a.sh got: foo
2 ./b.sh got: bar
3 ./a.sh got: bar
3 ./b.sh got: foo
4 ./a.sh got: foo
4 ./b.sh got: bar
...