Bash – How to Create a Redirection Loop

bashio-redirection

This one is mostly for academic reasons. 😉

Consider the following Bash statement:

foo | bar

This executes two processes, foo and bar, such that the standard-out of foo is connected to the standard-in of bar. So far, so good.

Is there a way to invoke foo and bar so that we also have a connection in the opposite direction?

foo.stdout -> bar.stdin
foo.stdin  <- bar.stdout

As I understand it, Bash calls pipe(2) to create a pair of FDs connected to each other, which can then be used to replace stdin / stdout in the respective child processes. At the kernel level, I see no reason why you couldn't call pipe(2) twice and arrange the circular arrangement depicted above.

But I can't figure out the shell syntax to actually do that.

So far, the best idea I can come up with is to use named pipes, or maybe to launch a subshell and do some crazy FD-to-FD redirecting somehow…

Best Answer

In bash, you can do it with either a coproc (bash has lousy support for multiple coprocs, but you only need one here):

#!/bin/bash
set -e
coproc { while read -r line; do echo "$BASHPID read: $line";  done; }
i=0; while :; do
    echo "$BASHPID writing>> $i"
    echo $i >&"${COPROC[1]}"
    read -r line <&"${COPROC[0]}"
    echo "$BASHPID coproc produced>> $line"
    i=$((i+1))
done

or named pipes (those work in simple POSIX shells too):

#!/bin/bash
set -e
trap 'rm -rf "$tmpd"' EXIT
tmpd=$(mktemp -d)

mkfifo "$tmpd/p0" "$tmpd/p1"
exec 3<>"$tmpd/p0"
exec 4<>"$tmpd/p1"
rm -rf "$tmpd"

( while read -r line; do echo "$BASHPID read: $line";  done; ) <&3 >&4  &
i=0; while :; do
    echo "$BASHPID writing>> $i" 
    echo $i >&3
    read -r line <&4
    echo "$BASHPID coproc produced>> $line"
    i=$((i+1))
done 

They both might seem ugly if you're not used to fd-handling in shells.

Also, due to the effect pipes have on scheduling (writing to pipes with a full pipe buffer blocks you as does reading from a pipe with an empty one), you might get deadlocked with certain read/write patterns.

Outputs of the above two examples might look like this:

32435 writing>> 0
32435 coproc produced>> 32441 read: 0
32435 writing>> 1
32435 coproc produced>> 32441 read: 1
32435 writing>> 2
32435 coproc produced>> 32441 read: 2
32435 writing>> 3
32435 coproc produced>> 32441 read: 3
32435 writing>> 4
32435 coproc produced>> 32441 read: 4
Related Question