Tailing in Python

May 16, 2010. Filed under python

Writing complicated systems is a fantastic feeling. You know, ideas so vast that you have to sit down with pen and paper just to think for a bit. You might even talk with a coworker instead of just imagining your solution is perfect right off the bat and implementing code_that_wont_get_used_for_various_reasons/misguided_solution.py.r9999.

Then there are other days. When you have lost the ability to function in any meaningful way and you end up putting a disproportionate amount of mental energy into crafting a Python version of.. I don't know.. tail.

If you too are staring at a blank wall in confusion on this fine Sunday, perhaps you can write your own Python implementation of tail which meets these requirements:

  • supports following newly appended lines to a file (without showing the existing lines of the file),
  • supports iterating through all the existing lines and then following new lines as they appear.

Judging criteria are:

  • correctness (working is better than not working)
  • simplicity (simple is better than complex)
  • conciseness (short is better than long)
  • performance (fast is better than slow)
  • style (mine is better than yours)

Well, get coding!

My Implementation

For your consideration, here is my solution.

import time
from optparse import OptionParser

SLEEP_INTERVAL = 1.0

def readlines_then_tail(fin):
    "Iterate through lines and then tail for further lines."
    while True:
        line = fin.readline()
        if line:
            yield line
        else:
            tail(fin)

def tail(fin):
    "Listen for new lines added to file."
    while True:
        where = fin.tell()
        line = fin.readline()
        if not line:
            time.sleep(SLEEP_INTERVAL)
            fin.seek(where)
        else:
            yield line

def main():
    p = OptionParser("usage: tail.py file")
    (options, args) = p.parse_args()
    if len(args) < 1:
        p.error("must specify a file to watch")
    with open(args[0], 'r') as fin:
        for line in readlines_then_tail(fin):
            print line.strip()

if __name__ == '__main__':
    main()

Now, where's yours?