10

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!

1
  • 1
    Hi and welcome to Stack Overflow! This is the best beginner question I've ever seen. Commented Feb 14, 2015 at 10:30

2 Answers 2

4

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!

Sign up to request clarification or add additional context in comments.

Comments

0

@zackse's explanation is spot on.

You can open a file descriptor in the script to keep netcat from seeing an EOF:

#!/bin/bash

# clean up on exit: close FD 3 and exit all backgrounded
# processes in this process group (see `kill(2)`)
trap 'rm -f fifo; exec 3>&-; kill 0' EXIT

mkfifo fifo

# netcat reads from `fifo`
busybox nc -v localhost 3000 <fifo &

# create FD 3 for writing to `fifo`; this keeps the reading end
# of the pipe open and prevents netcat from seeing EOF
exec 3>fifo

echo Sending...

echo foo >fifo
echo bar >fifo
echo baz >fifo

# wait for all background processes to exit
# allows you to send more data to netcat
wait

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.