0

I'm trying to write a simple python script that reads from a fifo and then writes to another fifo.

I have created two FIFOs using the following command:

$ mkfifo input
$ mkfifo output

I invoke the script using the following command:

$ tail -f input | stdbuf -oL ../entropyCalc/entropy.py > output

, observe the FIFO output using:

$ tail -f output

and then invoke a writer using the following command:

$ echo "/path/to/a/valid/file" >> input

The problem is I expect the fifo to output the result as soon as it processes the input file, but I only observe that when I invoke (execute) the script once, exit and then re-execute the script. Everything works fine after this.

In summary:
I don't see any output in the reader if I execute script -> spin up reader -> write to input fifo
But, the command tail -f output outputs the result when I execute the script -> spin up the reader -> write to fifo -> kill script -> re-execute the script

I'm not sure what causes this behaviour since I'd expect the system to write to the files as soon as the result is written to stdout. I'd have expected the buffering if I would not used stdbuf -oL which limits buffering to a line.

The python script is a simple entropy calculator:

#! /usr/bin/env python2

import sys, os
import numpy as np
from scipy.stats import entropy

while 1:
    try:
        line = sys.stdin.readline()
    except KeyboardInterrupt:
        break

    if not line:
        break

    line = line.strip()

    if not line == '':
        fname1 = open(line)
        fsize = os.path.getsize(line)
        f1 = np.frombuffer(fname1.read(fsize), dtype=np.uint8)
        value,counts = np.unique(f1, return_counts=True)
        print line,str(entropy(counts))
        sys.stdout.flush()

I'm using bash 4.4 on Ubuntu 18.04.3

  • 1
    You have unbuffered the script but not tail. Why not? unix.stackexchange.com/a/423645/70524 – muru Sep 26 '19 at 4:54
  • That doesn't work if I use tail -f as the reader, but it does if I use cat output, any reason why it behaves this way? – lol Sep 26 '19 at 5:03
  • your Q is way to deep for me, but you please explain the purpose of tail -f fifo | command .. in your 2nd codeblock? why not just <fifo command ..? – mosvy Sep 26 '19 at 5:15
-1

You used tail incorrectly.

What you observed is simply an indication that tail -f output works as advertised. And there are more pitfalls in your tail -f input usage.

Please note that these tail misuses will also break the operational integrity of your script. See below for details...

Short Explanation

Problems with your tail -f input usage:

  • It waits for end-of-file at first.
  • Once end-of-file is first reached (first filename-giver terminated), it would only pass last 10 filenames to the script; breaking operational integrity.
  • It buffers until reaching each end-of-file, making interactive/sparse filename-entering unusable.

Problems with your tail -f output usage:

  • It waits for end-of-file at first.
  • Once end-of-file is first reached (your script got killed the first time), it would give only 10 last lines of the script output so far; breaking operational integrity.

(Buffering is not a problem at tail -f output since its output leg is a terminal)

See Conclusion below for remedies.

Long Explanation

The job description of tail is essentially: "output 10 lines that is just before end-of-file". The -f switch only adds "and everything that got subsequently appended to it".

  • In order to accomplish its main goal, tail would have to keep reading the input, silently and endlessly (with only latest 10 lines kept in the buffer), until it got end-of-file. Then it would dump the last 10 lines from buffer to the output, and quit (or continue operating in -f phase).

Now, let me remind you that end-of-file on pipe never comes until the upstream program terminates1...

The result is tail -f output will, by design, keep silently buffering through your script's result, without emitting anything... until the moment you first terminated your script: an end-of-file got sent to tail -f output, that's when you started seeing your output.

  • But what you see is not the whole output so far, but rather just the last 10 lines of output at that point.

    If you asked your script to analyze 20 files (with less than 10 files at a time, see next point), you would get only the result of last 10 files analyzed just before you killed the script the first time. This breaks the expectation (i.e. integrity) of your script's operation, where result of every analysis done should have been reported on the output.

  • This problem also affect tail -f input. Dumping a file list with 11+ filenames into your input pipe at first time, will result in only 10 bottommost names reaching your script, breaking the integrity of your script's operation again. On second and later times, this would not be a problem.

And that is not even the end of the story...

Once reached the first end-of-file, tail -f would continue operating in -f phase, which means output everything that got subsequently appended to the input file.

In this phase, it would not close the input handle yet; it will register an inotify listener on the input file, and wait. Each time that any program writes to/closes the input file, tail would read the input handle again (and output everything it just read), until it encounters end-of-file2, and it will be back to waiting again. Rinse and repeat until you manually terminate tail -f.

  • A catch here is "output everything it just read" also subjected to usual output buffering when the output is not terminal.

    It does not affect your use of tail -f output per se (as its output leg is a terminal). tail -f input however, will be affected:

    • If you entered multiple filenames interactively with cat > input , you would see that processing won't start until you terminate cat.
    • And this means connecting input pipe to a long-running program that only occasionally emit filenames, won't work either.

Conclusion

In my understanding, you used tail -f input to shield your script from receiving end-of-file, so your script could keep running like a daemon, while filename-giver programs could just come and go.

So my advice is to use tail -n +1 -f instead, to make sure that tail don't go on endless hunts for end-of-file, and eliminate the last-10-line problem. Then use stdbuf to control buffering where it would be an issue3,4...

stdbuf -oL tail -n +1 -f input | ../entropyCalc/entropy.py > output 2>&1

Then on the monitoring side, use either:

tail -n +1 -f output

or:

while true; do cat output; done

Footnotes

  • 1 Or more precisely, when the upstream program closes the pipe. (In your case, it would only happen to tail -f output when your script terminates)
  • 2 Never happens to your tail -f output, unless you terminated your script the second time.
  • 3 Your script is already line-buffered on its output stage, so no need for stdbuf there.
  • 4 A standard error redirection 2>&1 will also ensure that script error also displays on the monitoring terminal.
| improve this answer | |
  • I'm not sure if I understood why you wrote that tail -f waits for EOF, a simple test for this is following: Step 1: create named fifo; mkfifo test. Step 2: Use tail -f to listen on the contents; tail -f test Step 3: Write something to the fifo without feedin it EOF; cat > test. Now, tail -f won't buffer or lose the data even if you don't write 10 lines or don't provide EOF. You can see your input being printed by tail -f immediately without any buffering. – lol Sep 27 '19 at 15:50
  • @lol, negative. I did the exact simple test steps you mentioned: tail -f didn't display a thing- screenshot A, screenshot B. After several minutes of silence, I ended cat > test with Ctrl+D; and last 10 lines instantly appeared on tail -f output, just like what I described- screenshot C. GNU Coreutils 8.13 tail, Linux kernel 3.2.65. – xwindows -on strike- Sep 28 '19 at 7:21
  • @lol, anyway, did you try the commands given in Conclusion yet? Did they somehow not work in your configuration? (They did work on mine, so any elaboration on how exactly they went wrong on yours would be useful) – xwindows -on strike- Sep 28 '19 at 7:29
  • This is very weird, so I tried the exact same thing as I mentioned in my first comment. tail -f at least on my machine buffers everything until it receives the first EOF after that it's line buffered. I'm using GNU coreutils 8.28 on Linux 5.0.0. – lol Sep 29 '19 at 4:51

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.