bash version 4 has coproc command that allows this done in pure bash without named pipes.
From bash man page about coproc command:
coproc [NAME] command [redirections]
This creates a coprocess named NAME.
If NAME is not supplied, the default name is COPROC.
The standard output of command is connected via a pipe
to a file descriptor in the executing shell, and that file
descriptor is assigned to NAME[0].
The standard input of command is connected via a pipe to a
file descriptor in the executing shell, and that file
descriptor is assigned to NAME[1].
Start with a version easy to understand (but does not work). Start entire pipeline under coproc and use extra cat for plumbing.
coproc {
cmd1 | cmd2
}
cat <&${COPROC[0]} >&${COPROC[1]}
Buffering is obviously something that needs to be taken care of. Here plain cat will likely break it due to buffering. So use stdbuf to disable buffering by cat:
# this is same as above
coproc {
cmd1 | cmd2
}
# replace last line from sample above with:
stdbuf -i0 -o0 cat <&${COPROC[0]} >&${COPROC[1]}
Better still, do away with cat. Break up pipeline into two steps, run 1st section under coproc and 2nd section to connect with 1st.
coproc cmd1
cmd2 <&${COPROC[0]} >&${COPROC[1]}
This still assumes that cmd1 and cmd2 handle buffering correctly to make it work.
Some other shells also can do coproc as well.
That is all the answer above.
Below is just some exploration and tests.
Here example chains three commands only to make it a little more interesting.
# start pipeline
coproc {
cmd1 | cmd2 | cmd3
}
# reconnect STDOUT from `cmd3` to STDIN of `cmd1`
stdbuf -i0 -o0 /bin/cat <&${COPROC[0]} >&${COPROC[1]}
Get rid of cat and stdbuf and stay with pure bash.
Break it up into two parts, launch the first pipeline under coproc, then launch second part (either a single command or a pipeline), reconnecting it to the first:
coproc {
cmd 1 | cmd2
}
cmd3 <&${COPROC[0]} >&${COPROC[1]}
Proof of concept
File prog - a worker program participating in looped IO. Just a dummy prog to consume, tag and re-print lines. It is using subshells to avoid buffering problems maybe overkill, it's not the point here.
#!/bin/bash
# Start this prog with only param "1" to produce some output
let c=0
sleep 2
[ "$1" == "1" ] && ( echo start )
while : ; do
read line
echo "$1:${c} ${line}" 1>&2
sleep 2
( echo "$1:${c} ${line}" )
let c++
[ $c -eq 3 ] && exit
done
File start_io_loop_with_cat - a demo launcher.
This is a version using bash, cat and stdbuf
#!/bin/bash
# start all 3 commands as one pipeline
coproc {
./prog 1 \
| ./prog 2 \
| ./prog 3
}
# start cat without buffering to connect IO loop
stdbuf -i0 -o0 /bin/cat <&${COPROC[0]} >&${COPROC[1]}
File start_io_loop_pure_bash - another demo launcher.
This is a version using pure bash only.
#!/bin/bash
# start first 2 of 3 commands in the pipeline
coproc {
./prog 1 \
| ./prog 2
}
# start 3rd command connecting IO to 1 and 2 to make the IO loop
./prog 3 <&${COPROC[0]} >&${COPROC[1]}
Output:
> ./start_io_loop_pure_bash
2:0 start
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
That does it.