using mkfifo in a shell script

glenn jackman picture glenn jackman · Apr 15, 2014 · Viewed 7.5k times · Source

I was trying to do something simple with a FIFO: read the lines, but not all at once, and it unexpectedly "did not work".

This is OK:

$ f=$(mktemp -u)
$ mkfifo $f
$ { seq 5 > $f; } &
[1] 2486
$ while read line; do echo $line; done < $f
1
2
3
4
5
[1]+  Done                    { seq 10 > $f; }

But if I try reading lines one-by-one, the first read succeeds and the 2nd read hangs.

$ { seq 5 > $f; } &
[1] 2527
$ read line < $f; echo $line
1
[1]+  Done                    { seq 5 > $f; }
$ read line < $f; echo $line
[hangs here...]

Can someone explain this? Why can't I read all 5 lines one-by-one? What happened to the rest of the data?


I discovered I can read line-by-line if I create a file descriptor to redirect the FIFO:

$ { seq 5 > $f; } &
[1] 2732
$ exec 3<$f
[1]+  Done                    { seq 5 > $f; }
$ read -u 3 line && echo $line || echo no more data
1
$ read -u 3 line && echo $line || echo no more data
2
$ read -u 3 line && echo $line || echo no more data
3
$ read -u 3 line && echo $line || echo no more data
4
$ read -u 3 line && echo $line || echo no more data
5
$ read -u 3 line && echo $line || echo no more data
no more data
$ exec 3<&-

I still don't understand the middle scenario. Can anyone explain?


Version info:

$ bash --version
GNU bash, version 4.2.25(1)-release (i686-pc-linux-gnu)
Copyright (C) 2011 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>

This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
$ mkfifo --version
mkfifo (GNU coreutils) 8.13
Copyright (C) 2011 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Written by David MacKenzie.

Answer

mustaccio picture mustaccio · Apr 15, 2014

I guess what happens is this:

$ read line < $f opens the FIFO for reading, reads one line, then closes the FIFO. Once the reader closes the FIFO on its side, the writer (seq 5 > $f) also closes. When you open the FIFO next time nobody writes to it at that point, so the read blocks.

With the while the FIFO is open for reading until the while command finishes, allowing the writer to send more lines to the FIFO.

You can use lsof -p $$ to verify what files are (not) open at each point.