I have two (UNIX) programs A and B that read and write from stdin/stdout.
My first problem is how to connect the stdout of A to stdin of B and the stdout of B to the stdin of A. I.e., something like A | B but a bidirectional pipe. I suspect I could solve this by using exec to redirect but I could not get it to work. The programs are interactive so a temporary file would not work.
The second problem is that I would like to duplicate each direction and pipe a duplicate via a logging program to stdout so that I can see the (text-line based) traffic that pass between the programs. Here I may get away with tee >(...) if I can solve the first problem.
Both these problems seems like they should have well known solutions but I have not be able to find anything.
I would prefer a POSIX shell solution, or at least something that works in bash on cygwin.
Thanks to your answers I came up with the following solution. The A/B commands uses nc to listen to two ports. The logging program uses sed (with -u for unbuffered processing).
bash-3.2$ fifodir=$(mktemp -d)
bash-3.2$ mkfifo "$fifodir/echoAtoB"
bash-3.2$ mkfifo "$fifodir/echoBtoA"
bash-3.2$ sed -u 's/^/A->B: /' "$fifodir/echoAtoB" &
bash-3.2$ sed -u 's/^/B->A: /' "$fifodir/echoBtoA" &
bash-3.2$ mkfifo "$fifodir/loopback"
bash-3.2$ nc -l -p 47002 < "$fifodir/loopback" \
| tee "$fifodir/echoAtoB" \
| nc -l -p 47001 \
| tee "$fifodir/echoBtoA" > "$fifodir/loopback"
This listens for connection to port 47001 and 47002 and echos all traffic to standard output.
In shell 2 do:
bash-3.2$ nc localhost 47001
In shell 3 do:
bash-3.2$ nc localhost 47002
Now lines entered in shell 2 will be written to shell 3 and vice versa and the traffic logged to shell 1, something like:
B->A: input to port 47001
A->B: input to port 47002
The above has been tested on Cygwin
Update: The script above stopped working after a few days(!). Apparently it can deadlock. Some of the suggestions in the answers may be more reliable.
http://bisqwit.iki.fi/source/twinpipe.html
How about a named pipe?
# mkfifo foo
# A < foo | B > foo
# rm foo
For your second part I believe tee is the correct answer. So it becomes:
# A < foo | tee logfile | B > foo
You could probably get away with named pipes:
mkfifo pipe
gawk '$1' < pipe | gawk '$1' > pipe
You can use Expect.
Expect is a tool for automating interactive applications such as telnet, ftp, passwd, fsck, rlogin, tip, etc.
You could use the following code (taken from the Exploring Expect book) as a starting point - it connects the output of proc1 to the input of proc2 and vice versa, as you requested:
#!/usr/bin/expect -f
spawn proc1
set proc1 $spawn_id
spawn proc2
interact -u $proc1
I spent a lot of time on this, gave it up, and last decided to use ksh (the Korn shell), which allows this.
cmd1 |& cmd2 >&p <&p
where |&
is a (pipe) operator to start a co-process and &p
is file descriptor of that co-process.
I had this problem at one point, and I threw together this simple C program.
#include <stdio.h>
#include <unistd.h>
#define PERROR_AND_DIE(_x_) {perror(_x_); _exit(1);}
int main(int argc, char **argv) {
int fd0[2];
int fd1[2];
if ( argc != 3 ) {
fprintf(stdout, "Usage %s: \"[command 1]\" \"[command 2]\"\n", argv[0]);
_exit(1);
}
if ( pipe(fd0) || pipe(fd1) ) PERROR_AND_DIE("pipe")
pid_t id = fork();
if ( id == -1 ) PERROR_AND_DIE("fork");
if ( id ) {
if ( -1 == close(0) ) PERROR_AND_DIE("P1: close 0");
if ( -1 == dup2(fd0[0], 0) ) PERROR_AND_DIE("P1: dup 0"); //Read my STDIN from this pipe
if ( -1 == close(1) ) PERROR_AND_DIE("P1: close 1");
if ( -1 == dup2(fd1[1], 1) ) PERROR_AND_DIE("P1: dup 1"); //Write my STDOUT here
execl("/bin/sh", "/bin/sh", "-c", argv[1], NULL);
PERROR_AND_DIE("P1: exec")
}
if ( -1 == close(0) ) PERROR_AND_DIE("P2: close 0");
if ( -1 == dup2(fd1[0], 0) ) PERROR_AND_DIE("P2: dup 0");
if ( -1 == close(1) ) PERROR_AND_DIE("P2: close 1");
if ( -1 == dup2(fd0[1], 1) ) PERROR_AND_DIE("P2: dup 1");
execl("/bin/sh", "/bin/sh", "-c", argv[2], NULL);
PERROR_AND_DIE("P2: exec")
}
This question is similar to one I asked before. The solutions proposed by others were to use named pipes, but I suspect you don't have them in cygwin. Currently I'm sticking to my own (attempt at a) solution, but it requires /dev/fd/0
which you probably also don't have.
Although I don't really like the passing-command-lines-as-strings aspect of twinpipe
(mentioned by JeeBee (139495)), it might be your only option in cygwin.
I'd suggest "coproc":
#! /bin/bash
# initiator needs argument
if [ $# -gt 0 ]; then
a=$1
echo "Question $a"
else
read a
fi
if [ $# -gt 0 ]; then
read a
echo "$a" >&2
else
echo "Answer to $a is ..."
fi
exit 0
Then see this session:
$ coproc ./dialog
$ ./dialog test < /dev/fd/${COPROC[0]} > /dev/fd/${COPROC[1]}
Answer to Question test is ...