I made a file descriptor using
mkfifo fifo
As soon as something is written to this pipe, I want to reuse it immediately. Should I use
tail -f fifo
or
while true; do cat fifo; done
?
They seem to do the same thing and I could not measure a difference in performance. However, when a system does not support inotify (Busybox, for example), the former needs to be
tail -f -s 0 fifo
But this eats up the CPU with 100% usage (test it out: mkfifo fifo && busybox tail -f -s 0 fifo & echo hi>fifo
/ cancel with fg 1
and CtrlC). So is the while-true-cat the more reliable solution?
Best Answer
When you do:
Assuming no other process has opened the
fifo
for writing yet,cat
will block on theopen()
system call. When another process opens the file for writing, a pipe will be instantiated andopen()
will return.cat
will callread()
in a loop andread()
will block until some other process writes data to the pipe.cat
will see end-of-file (eof) when all the other writing processes have closed their file descriptor to thefifo
. At which pointscat
terminates and the pipe is destroyed¹.You'd need to run
cat
again to read what will be written after that to thefifo
(but via a different pipe instance).In:
Like
cat
,tail
will wait for a process to open a file for writing. But here, since you didn't specify a-n +1
to copy from the beginning,tail
will need to wait until eof to find out what the last 10 lines were, so you won't see anything until the writing end is closed.After that,
tail
will not close its fd to the pipe which means the pipe instance won't be destroyed, and will still attempt to read from the pipe every second (on Linux, that polling can be avoided via the use ofinotify
and some versions of GNUtail
do that there). Thatread()
will return with eof (straight away, which is why you see 100% CPU with-s 0
(which with GNUtail
means to not wait betweenread()
s instead of waiting for one second)) until some other process opens the file again for writing.Here instead, you may want to use
cat
, but make sure the pipe instance always stays around after it has been instantiated. For that, on most systems, you could do:cat
's stdin will be open for both reading and writing which meanscat
will never see eof on it (it also instantiates the pipe straight away even if there's no other process opening thefifo
for writing).On systems where that doesn't work, you can do instead:
That way, as soon as some other process opens the
fifo
for writing, the first read-onlyopen()
will return, at which point the shell will do the write-onlyopen()
before startingcat
, which will prevent the pipe from ever being destroyed again.So, to sum up:
cat file
, it would not stop after the first round.tail -n +1 -f file
: it would not do a uselessread()
every second after the first round, there would never be eof on the one instance of the pipe, there would not be that up to one second delay when a second process opens the pipe for writing after the first one has closed it.tail -f file
. In addition to the above, it would not have to wait for the first round to finish before outputting something (only the last 10 lines).cat file
in a loop, there would be only one pipe instance. The race windows mentioned in ¹ would be avoided.¹ at this point, in between the last
read()
that indicates eof andcat
terminating and closing the reading end of the pipe, there is actually a small windows during which a process could open thefifo
for writing again (and not be blocked as there's still a reading end). Then, if it writes something aftercat
has exited and before another process opens thefifo
for reading, it would get killed with a SIGPIPE.