Bash – What are the difference between those four commands (fifo, process substitution, redirection…)

bashfifoio-redirectionprocess-substitutionshell

My goal is to create a simple echo server using nc and a single fifo. I'm not looking for the best way to do it, I'm merely trying to understand the semantics of the following commands (when does fork happen, why, what does it change, why the commands behave differently…).

I'm using Bash, so I'm not sure if all of the commands will work with a POSIX sh or zsh, ksh, …

Here are the four commands I'm mentioning in the title (assuming that I already did mkfifo fifo):

cat fifo | nc -l localhost 8888 > fifo
exec 3<> fifo && nc -l localhost 8888 <&3 >&3 && exec 3>&-
nc -l localhost 8888 <(cat fifo) > fifo
nc -l localhost 8888 < fifo > fifo

Now I would expect the 4 commands to do the same thing, at the very least the two last ones to do the same thing.

  1. The first command behaves as expected, a simple echo server that shuts down when the client closes the connection.
  2. Behaves like 1.
  3. I can connect to the server, send data, but I never receive anything back. When I close the client connection, the server shuts down.
  4. Can't connect to the server, the server listens forever.

Best Answer

The key here is that opening a FIFO is a blocking operation. The open only returns once both ends are connected, i.e. once the fifo is open for both reading and writing.

man fifo(7)

Normally, opening the FIFO blocks until the other end is opened also.

In the 1st case, the shell forks to execute the pipeline, so opening the fifo for reading (cat fifo) and opening the fifo for writing (> fifo) happen in separate processes, so happen independently.

In the 2nd case, the open for reading and open for writing (3<>fifo) happen in a single step.

In the 3rd case, <(cat fifo) expands to a filename, e.g. /dev/fd/42. So it's like you're running nc -l localhost 8888 /dev/fd/42 > fifo. You need an extra < for it to be equivalent, e.g. nc -l localhost 8888 < <(cat fifo) > fifo.

In the 4th case, the shell is trying to open the fifo for reading (< fifo) and open it for writing (> fifo) as part of the same process. The shell does them one at a time, left to right. So it tries to open fifo for reading, and blocks forever, waiting for something to open fifo for writing. I think you'll find that in this case, nc never even got started, and the port was never opened for listening.

Related Question