Here is a little test you can do to play around with FIFOs (also known as Named Pipes) in bash.
For setup, we run
mkfifo test
which creates a FIFO named test in the current directory.
Then run
cat - < test
This opens test for reading and connects it to stdin, then runs cat -,
which copies stdin to stdout. Nothing will happen (for now) because there
are no writers for test.
In a separate window, execute
echo "hi" > test
You will see your first window print “hi” and then the cat command will
exit.
The reason that cat exits is because as soon as the echo command
finishes, there are no writers connected to test. In that case, anyone who
tries to read from the pipe will get an EOF, which is what cat uses to know
it should move on to the next file or exit. You can see this in action by
running
sleep 60 > test &
Now you can run as many “echo” commands as you want (for 60 seconds) and
cat will keep running.
The reason that cat doesn’t just return before the first echo (after all,
there are no writers at the beginning) is that Bash tells the OS to wait for a
writer to connect before executing its command. You can see this by replacing
cat - with
{ echo "starting"; cat - } < test
You will see that “starting” is not written to stdout until the first
writer is connected (either the first echo or the sleep command).
A better way than sleep to keep a writer open is to use a read-write file
descriptor:
cat - 3<> test < test
This starts by opening test for reading and writing and connecting
it to file descriptor 3. Then it also opens it for reading and connects
it to stdin.
We won’t actually use file descriptor 3 to pass data, but the <> operator
is nonblocking (so it doesn’t matter that there are no other readers or
writers yet). It remains open for the life of the cat command, so cat
will start immediately and never see EOF on its stdin.
I started on this investigation while trying to run a persistent Minecraft
server for my kids. Minecraft works by writing logs to stdout and reading
commands from stdin.
My runit script to keep this running in the background looks like this (omitting some details such as error handling and the java options):
#!/bin/sh
cd /var/opt/minecraft
mkfifo fifo
exec chpst -u minecraft java \
-jar /opt/minecraft/server.jar \
nogui 3<> fifo < fifo
Then if I need to send a command to the running server, I can run, for example,
echo "op user0293" | sudo tee /var/opt/minecraft/fifo