Well, its fairly "easy" with named pipes (
mkfifo). I put easy in quotes because unless the programs are designed for this, deadlock is likely.
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
Now, there is normally buffering involved in writing stdout. So, for example, if both programs were:
print while (<>);
you'd expect a infinite loop. But instead, both would deadlock; you would need to add
$| = 1 (or equivalent) to turn off output buffering. The deadlock is caused because both programs are waiting for something on stdin, but they're not seeing it because its sitting in the stdout buffer of the other program, and hasn't yet been written to the pipe.