I'm trying to create a persistent connection using bash. On terminal 1, I keep a netcat running as a server:
$ nc -vlkp 3000
Listening on [0.0.0.0] (family 0, port 3000)
On terminal 2, I create a fifo and keep a cat:
$ mkfifo fifo
$ cat > fifo
On terminal 3, I make the fifo as an input to a client netcat:
$ cat fifo | nc -v localhost 3000
Connection to localhost 3000 port [tcp/*] succeeded!
On terminal 4, I send whatever I want:
$ echo command1 > fifo
$ echo command2 > fifo
$ echo command3 > fifo
Going back to terminal 1, I see the commands being received:
$ nc -vlkp 3000
Listening on [0.0.0.0] (family 0, port 3000)
Connection from [127.0.0.1] port 3000 [tcp/*] accepted (family 2, sport 41722)
command1
command2
command3
So, everything works. But when I put that in a script (I called that fifo.sh), bash is not able to write into fifo:
On terminal 1, same listening server:
$ nc -vlkp 3000
Listening on [0.0.0.0] (family 0, port 3000)
On terminal 2, I run the script:
#!/bin/bash
rm -f fifo
mkfifo fifo
cat > fifo &
pid1=$!
cat fifo | nc -v localhost 3000 &
pid2=$!
echo sending...
echo comando1 > fifo
echo comando2 > fifo
echo comando3 > fifo
kill -9 $pid1 $pid2
The output in terminal 2 is:
$ ./fifo.sh
Connection to localhost 3000 port [tcp/*] succeeded!
sending...
On terminal 1 I see only the connection. No commands:
$ nc -vlkp 3000
Listening on [0.0.0.0] (family 0, port 3000)
Connection from [127.0.0.1] port 3000 [tcp/*] accepted (family 2, sport 42191)
Connection closed, listening again.
Any idea on why it only works interactively? Or is there any other way to create a persistent connection using only Bash? I don't want to go for Expect because I have a bigger Bash script that does some work after sending the command1, and command2 depends on command1 output, etc.
Thank you!
When a process is started in the background in a script, standard input is redirected from /dev/null
. This means the first cat
command will read and emit EOF
as soon as it executes, which will cause netcat to exit immediately after starting, so the output later in the script will never make it to the fifo, because there isn't an active listener at that time.
In this case, when cat > fifo
is evaluated, the shell forks a child process, redirects standard input from /dev/null
, and attempts to open fifo
for write. The child remains in a blocking open
call at this time. Note that cat
is not executed until after the open
call completes.
Next, cat fifo | nc -v localhost 3000
is spawned. cat
opens fifo
for read, which allows the blocking open
from the first child to complete and the first cat
to execute.
The first cat
inherits its parent's file descriptors, so its standard input is attached to /dev/null
and it thus reads and emits an EOF
immediately. The second cat
reads the EOF
and passes that to the standard input of nc
, which causes netcat to exit.
By the time the echo
statements are evaluated, the processes identified by $pid1
and $pid2
are finished. Since there is no longer a listener on fifo
, the first echo
will block forever.
I don't have a pure-shell fix, but you can use an external program like perl to open the placeholder writer to fifo
instead of using shell redirection. Aside, please note that there is a race with nc
starting after the echo
statements (where the kill
happens before netcat has a chance to process input/send output), so here I added a delay after the cat | nc
expression. There is almost certainly a better solution out there, but here's what I came up with:
#!/bin/bash
rm -f fifo
mkfifo fifo
perl -e 'open(my $fh, ">", "fifo"); sleep 3600 while 1' &
pid1=$!
cat fifo | nc -v localhost 3000 &
pid2=$!
sleep 2
echo sending...
echo comando1 > fifo
echo comando2 > fifo
echo comando3 > fifo
kill -9 $pid1 $pid2
Hope this helps, great question!