9

This script:

#!/bin/bash
tmppipe=/tmp/temppipe
mkfifo $tmppipe
echo "test" > $tmppipe
cat $tmppipe
exit

does not terminate. I assume that the cat command is waiting for an EOF from the pipe; how do I send one?

2 Answers 2

13

No, it's

echo test > "$tmppipe" # BTW, you've got the quotes in the wrong places

that hangs. More precisely, it's the shell opening the pipe for writing before running echo.

pipe are inter-process communication mechanisms, they are to be used between processes running concurrently. Here, the open(WR_ONLY) (>) will block until another process does an open in read mode.

echo test > "$tmppipe" &
cat < "$tmppipe"

will work because echo and cat run concurrently.

On Linux, you can get away with:

exec 3<> "$tmppipe" 4< "$tmppipe"
echo test >&3
exec 3>&-
cat <&4

That works because read+write opens (<>) on pipes don't block on Linux, and because the test\n output by echo is small enough to fit in the pipe, so you can do the write and the read sequentially.

It wouldn't work for a larger output like:

exec 3<> "$tmppipe" 4< "$tmppipe"
seq 100000 >&3
exec 3>&-
cat <&4

Because seq would fill up the pipe (64kiB in current versions of Linux) and block until some other process reads data off that pipe, which will never happen because cat won't run until seq has finished.

Note that:

echo test 1<> "$tmppipe"
cat < "$tmppipe"

would not work either because the echo command line would open the pipe, write test and then close the pipe (and then the system would destroy it as there's no file descriptor open to it anymore). So the next cat command line would try to instantiate a new pipe (and block until something opens the fifo file for writing).

6
  • Position of quotes really doesn't matter in this case, as $tmppipe contains no special characters. I just habitually put quotes around the argument to echo for readability - I'm used to other languages where string literals require quotes, so this way it is visually clear that "test" is a string being echoed.
    – Benubird
    Jun 5, 2015 at 10:29
  • @Benubird, note that it's not only about special (wildcard) characters, it's also with any character contained in $IFS. Leaving a variable unquoted (unless you have a very good reason to) is bad practice leading to bugs and potential security issues. Quotes in shell have almost an opposite meaning as with other languages. Jun 5, 2015 at 10:52
  • Great answer, but I was struggling with your second example (exec 3<> "$tmppipe"). As you explained here, cat < "$tmppipe" will never reach EOF because the writer still exists. For it to work properly we need to open separate file descriptors for reading and writing, like so: exec 3<>"$tmppipe" 4>"$tmppipe" 5<"$tmppipe" 3>&- and then close the write descriptor for the duration of reading: exec 4>&-; cat <&5; exec 4>"$tmppipe";.
    – EvgenKo423
    Feb 11, 2021 at 14:00
  • @EvgenKo423, you're right. I've now changed the code so cat terminates using a different approach. Feb 11, 2021 at 14:14
  • Something I don't understand... When cat <&4 is executed, I guess fd 4 gets closed (no need to perform exec 4<&- right?). But it is fd 0 of cat that receives the EOF status and closes. Is fd 4 closed as well because it is a duplicate of fd 0 of cat? Are there still two "synchronized" file descriptors here, or is it in fact the same object with two different labels (0 and 4, valid in their own respective context) pointing at that same object? I mean, why executing exec 3>&- doesn't close fd 4? Because there is still data in the named pipe?
    – The Quark
    Nov 2, 2021 at 16:00
2

Turns out the answer is obvious - the pipe is being locked by echo, and never reaches cat!

Pipes do not store data. When a process attempts to write to a pipe, the write cannot complete until there is something attached to the other end of the pipe, to read it.

A way to solve this particular example, is to use

echo "test" > $tmppipe &

to make the writing process run in the background. That way it sits there any waits, while the script continues, until it reaches the cat and can complete.

1
  • Strictly speaking, pipes store a buffer-full of data (the contenance of the pipe as in a real-life pipe), but pipes only live as long as there are file descriptors open to them. For fifo files, pipes are intentiated only when both a fd for reading and one for writing have been open to the file (or one fd in read+write on some systems), subsequent opens (of the same fifo file) while that pipe is active reuse the same pipe. Note that here, it's not writing to the pipe that blocks, but open it first (before running echo) Jun 5, 2015 at 10:27

You must log in to answer this question.

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