24

I have a program that exits automatically upon reading an EOF in a given stream ( in the following case, stdin ).
Now I want to make a shell script, which creates a named pipe and connect the program's stdin to it. Then the script writes to the pipe several times using echo and cat ( and other tools that automatically generates an EOF when they exit ). The problem I'm facing is, when the first echo is done, it sends an EOF to the pipe and make the program exit. If I use something like tail -f then I can't send an EOF when I intend to quit the program. I'm researching a balanced solution but to no avail.
I've already found both how to prevent EOFs and how to manually send an EOF but I can't combine them. Is there any hint?

#!/bin/sh
mkfifo P
program < P & : # Run in background
# < P tail -n +1 -f | program
echo some stuff > P # Prevent EOF?
cat more_stuff.txt > P # Prevent EOF?
send_eof > P # How can I do this?
# fg

3 Answers 3

25

As others have indicated, the reader of a pipe receives EOF once there are no writers left. So the solution is to make sure there is always one writer holding it open. That writer doesn't have to send anything, just hold it open.

Since you're using a shell script, the simplest solution is to tell the shell to open the pipe for writing. And then close it when you're done.

#!/bin/sh
mkfifo P
exec 3>P # open file descriptor 3 writing to the pipe
program < P
# < P tail -n +1 -f | program
echo some stuff > P
cat more_stuff.txt > P
exec 3>&- # close file descriptor 3

Note that if you omit the last line, file descriptor 3 will be automatically closed (and thus the reader receive EOF) when the script exits. Aside from convenience, this also provides a safety of sorts if the script were to somehow terminate early.

5
  • 6
    this exec 3>P cause hang in bash, why?
    – Wang
    Dec 4, 2017 at 16:25
  • @Wang It shouldn't. If it does then you're probably not doing the same thing as the as the POC code in the question. The only reason I can think that it would block is if you're instead doing something like exec 2>P, and you have trace mode turned on (set -x), in which bash is going to write to the pipe, but there's no reader so it blocks waiting for something to read.
    – phemmer
    Feb 19, 2018 at 13:45
  • 6
    @Wang @Patrick Indeed exec 3>P hangs in bash on my machine too. This is because there is no process reading from P. Thus, the solution was to swap lines exec 3>P and program < P & (adding the ampersand so that program runs in background).
    – macieksk
    Oct 17, 2018 at 9:04
  • 2
    @macieksk How about exec 3<>P? It counts as a writer and as a reader. Useful in case one wants program in the foreground. Feb 14, 2021 at 3:39
  • fails when the reader is python interpreter: it does not execute the input commands!
    – xtof54
    Dec 31, 2022 at 10:02
3

A pipe receives EOF when the last writer goes away. To avoid this, make sure that there is always a writer (a process that has the pipe open for writing, but doesn't actually write anything). To send the EOF, make that reserve writer go away.

mkfifo P
while sleep 1; do :; done >P &
P_writer_pid=$!
send_eof_to_P () {
  kill $P_writer_pid
}
1

There is no way for the program to distinguish between an EOF that means "it's time to quit" and an EOF that means "a writer is done, but there may be more input from someone else".

If you have the ability to modify the behaviour of your program, then do the reading in an infinite loop (one iteration lasts until EOF) and send it a specific command string that means "time to quit". Sending that string would be the task of the send_eof command in your question.

Another option:

( echo some stuff; cat more_stuff.txt ) >P

or

{ echo some stuff; cat more_stuff.txt; } >P

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .