Search code examples
bashpipeshipcfifo

How do I use a single named pipe with multiple processes in a daemon - client IPC in Bash?


I am trying to write a script to initiate a daemon which while pipe its output and input via two named pipes $FIFO.out and $FIFO.in respectively. What I wish to do is to give the client process the ability to talk with the daemon whenever it wants by using the named pipes.

Here is what I have already implemented: (The daemon is a qalc process)

#!/bin/sh
# Daemon script

FIFO="/tmp/qcalc"
mkfifo "$FIFO.out" "$FIFO.in"
exec qalc -t -u8 <"$FIFO.in" >"$FIFO.out"
#!/bin/sh
# Client script

FIFO="/tmp/qcalc"

talk() {
    echo "$1" >"$FIFO.in"

    # I'm not interested in the first two lines
    read -r _ <"$FIFO.out"
    read -r _ <"$FIFO.out"

    read -r result <"$FIFO.out"
    echo "$result"
}

talk 1
talk 2

The problem with this implementation is that I can only talk to the daemon once, and after that the client just freezes at the blocking read. Here is how it looks in the shell:

$ ./daemon &
[2] 57848
$ ./client
1
[2]  + done       ./daemon
^C./client: 13: cannot open /tmp/qcalc.out: Interrupted system call

Solution

  • This was a nice edge case when using named pipes and I learned a lot trying to get this system to work:

    Firstly, here's the final implementation I wrote:

    #!/bin/sh
    # Daemon script
    
    FIFO="/tmp/qcalc"
    mkfifo "$FIFO.in" "$FIFO.out"
    
    qalc -t -u8 <"$FIFO.in" >"$FIFO.out" &
    qalcPID="$!"
    exec 3>"$FIFO.in" # Dummy writer to keep the pipe alive
    wait "$qalcPID"
    exec 3>&-
    
    #!/bin/sh
    # Client script
    
    FIFO="/tmp/qcalc"
    
    echo "$1" >"$FIFO.in"
    
    exec 3<"$FIFO.out"
    read -r _ <&3
    read -r _ <&3
    read -r result <&3
    read -r _ <&3
    echo "$result"
    exec 3<&-
    

    What I understood from another SE question about pipes was that reading end pipe will get an EOF or it will be closed if the writing end is not connected.

    As @PCDSandwichMan correctly pointed out in his answer, my previous attempt failed because qalc was closing when it detected $FIFO.in closing. Therefore, to keep the pipe alive, I added a dummy writer after qalc is launched.

    qalc -t -u8 <"$FIFO.in" >"$FIFO.out" &
    exec 3>"$FIFO.in" # Dummy writer to keep the pipe alive
    

    The writer has to be added after a reader for the pipe is established so I launch qalc as a background process and then do it.