tags:

views:

570

answers:

6

I need to emulate "tail -f" in python, but I don't want to use time.sleep in the reading loop. I want something more elegant like some kind of blocking read, or select.select with timeout, but python 2.6 "select" documentation specifically says: "it cannot be used on regular files to determine whether a file has grown since it was last read." Any other way? In a few days if no solution is given I will read tail's C source code to try to figure it out. I hope they don't use sleep, hehe Thanks.

MarioR

+1  A: 

Why don't you just use subprocess.call on tail itself?

subproces.call(['tail', '-f', filename])

Edit: Fixed to eliminate extra shell process.

Edit2: Fixed to eliminate deprecated os.popen and thus the need to interpolate parameters, escape espaces and other stuff, and then run a shell process.

alex tingle
no tail on windows
Tzury Bar Yochay
-1: popen is the wrong way - it invokes a new shell process needlessy just to run the tail program.
nosklo
nosklo: You are right. I've fixed it to use exec. This way you get the benefit of the shell for command line parsing, but don't have the overhead of the extra process.
alex tingle
The shell command line parsing is not a benefit. You're interpolating the tail command, the parameter and file name into a single string, and then running a shell process to separate them again. And by doing that you also need to quote shell special characters like spaces yourself (you're doing it using single quotes). Isn't that extra work for nothing? What if the filename itself has quotes on it? You'll have to backslash-escape? Isn't it better to just do `subprocess.call(['tail', '-f', filename])` ?? No shell, no joining of parameters so the shell can split them, no quoting of characters.
nosklo
You are absolutely right, subprocess.call is certainly better.
alex tingle
`subprocess.Popen` does not necessarily create a shell -- if passed an array rather than a string and not given `shell=True`, it performs a direct exec of the invoked process just as `subprocess.call` does here.
Charles Duffy
+3  A: 

When reading from a file, your only choice is sleep (see the source code). If you read from a pipe, you can simply read since the read will block until there is data ready.

The reason for this is that the OS doesn't support the notion "wait for someone to write to a file". Only recently, some filesystems added an API where you can listen for changes made to a file but tail is too old to use this API and it's also not available everywhere.

Aaron Digulla
Why the downvote?
Aaron Digulla
+4  A: 

(update) Either use FS monitors tools

Or a single sleep usage (which I would you consider as much more elegant).

import time
def follow(thefile):
    thefile.seek(0,2)      # Go to the end of the file
    while True:
         line = thefile.readline()
         if not line:
             time.sleep(0.1)    # Sleep briefly
             continue
         yield line

logfile = open("access-log")
loglines = follow(logfile)
for line in loglines:
    print line
Tzury Bar Yochay
Heh, I was about to post almost exactly the same code (although not as a generator, which is much more elegant), +1
dbr
Why not `if line: yield line time.sleep(0.1)`? It may cause extra sleeps, but it's not really a big deal (in my opinion).
Chris Lutz
if it causes extra sleeps, why yes then?
Tzury Bar Yochay
A: 

IMO you should use sleep, it works on all platform and code will be simple

Otherwise you can use platform specific APIs which can tell you when file change e.g. on window use FindFirstChangeNotification on folder and watch for FILE_NOTIFY_CHANGE_LAST_WRITE events

On linux i think you can use i-notify

On Mac OSX use FSEvents

Anurag Uniyal
A: 

If you can use GLib on all platforms, you should use glib.io_add_watch; then you can use a normal GLib mainloop and process events as they happen, without any polling behavior.

http://library.gnome.org/devel/pygobject/stable/glib-functions.html#function-glib--io-add-watch

kaizer.se
+1  A: 

You can see here how to do a "tail -f" like using inotify.

dugres