350

I have a log file being written by another process which I want to watch for changes. Each time a change occurs I'd like to read the new data in to do some processing on it.

What's the best way to do this? I was hoping there'd be some sort of hook from the PyWin32 library. I've found the win32file.FindNextChangeNotification function but have no idea how to ask it to watch a specific file.

If anyone's done anything like this I'd be really grateful to hear how...

[Edit] I should have mentioned that I was after a solution that doesn't require polling.

[Edit] Curses! It seems this doesn't work over a mapped network drive. I'm guessing windows doesn't 'hear' any updates to the file the way it does on a local disk.

martineau
  • 99,260
  • 22
  • 139
  • 249
Jon Cage
  • 33,172
  • 32
  • 120
  • 206
  • 1
    on Linux one could use `strace` monitoring `write` calls for this – test30 Mar 30 '16 at 09:28
  • [@simao's answer](http://stackoverflow.com/a/4690739/52074) uses python-watchdog. Python-Watchdog has great documentation --> here is a link to the ["QuickStart"] documentation which provides a minimal code example that watches the current working directory. – Trevor Boyd Smith May 01 '17 at 14:36

26 Answers26

300

Did you try using Watchdog?

Python API library and shell utilities to monitor file system events.

Directory monitoring made easy with

  • A cross-platform API.
  • A shell tool to run commands in response to directory changes.

Get started quickly with a simple example in Quickstart...

gnat
  • 6,199
  • 101
  • 49
  • 71
simao
  • 12,131
  • 8
  • 48
  • 59
  • 57
    Installable with `easy_install`? Check. Free license? [Check](https://github.com/gorakhargosh/watchdog/blob/master/LICENSE). Solves the problem on the big platforms? [Check](http://packages.python.org/watchdog/installation.html#supported-platforms-and-caveats). I endorse this answer. Only note: the [example on their project page](http://packages.python.org/watchdog/quickstart.html#a-simple-example) doesn't work out of the box. Use [the one on their github](https://github.com/gorakhargosh/watchdog#example-api-usage) instead. – Inaimathi Oct 22 '12 at 20:25
  • 7
    We use watchdog. We may switch to QFileSystemWatcher. Just a fair warning- watchdog is good but far from perfect on all platforms (at this time). Each OS has it's idiosyncrasies. So, unless you are dedicated to making it perfect you will be pulling your hair out. If you are just looking to watch 10 files or so, I'd poll. OS disk caching is very mature and Watchdog involves polling APIs anyhow. It's mainly for watching huge folder structures IMHO. – SilentSteel Oct 15 '13 at 23:29
  • 3
    My one gripe with watchdog is, that it has many dependencies. Fewer than PyQt, of course, but it doesn't work and feel like the minimal, best practice, does-one-job-and-does-it-right solution. – AndreasT Jan 15 '14 at 12:36
  • 1
    Is @denfromufa correct here? Does watchdog really lock files, so they can't be edited concurrently to watchdog watching them? I can hardly believe that, it would be completely useless. – Michel Müller Dec 09 '15 at 23:24
  • 1
    @MichelMüller I just checked this example (see link below) and it works! not sure what was wrong before, but this answer does not provide any example. http://stackoverflow.com/a/18599427/2230844 – denfromufa Dec 10 '15 at 00:24
  • @denfromufa Thank you! So, does that revise your opinion on watchdog, would you use it in your projects if you really absolutely need to watch file changes? For my purposes (lots of reads that need to be fast, not many writes) I've come up with the following scheme: 1) poll directory modification date on every request --> tells me whether files have been added, deleted or renamed. 2) in case of changes in (1), refresh in-memory file index. 3) poll the file modification date for the set of files I'm interested in (e.g. first 10 files when sorting them in descending order). 4) read changed files – Michel Müller Dec 10 '15 at 02:32
  • @MichelMüller i want not mind using watchdog in my projects, seems pretty cross-platform as well – denfromufa Dec 10 '15 at 21:21
  • Watchdog looks like a poor choice. It seems to only work with directories (I tested in Windows) and gave different results in Cygwin. – Anders Petersson Jan 12 '21 at 15:53
  • So is watchdog really just a loop that keeps checking file properties as per @SilentSteel comment? Disappointed, i thought it somehow sits and just gets triggered by the changes subscribed to. – West Feb 23 '21 at 18:55
  • Nope @West to clarify, Watchdog is for real-time monitoring. It works by polling lower level OS file system APIs... Each OS has different APIs for realtime changes and Watchdog tries to abstract it to be cross platform. (In a nutshell.) Overall it works well. We built sync software that sync'd GB of data in realtime without much scanning on Win & Mac. Some corner cases would trigger a scan. Ex: OS buffers for file changes used to be limited (not sure if still the case) so if you dont grab the data you can lose it, in high volume. It needs a long-term commitment, but works. – SilentSteel Feb 24 '21 at 20:13
  • To clarify my comment: "Some corner cases would trigger a scan", I meant we chose to do a full "manual" file system scan in some cases where Watchdog had quirks. Watchdog will not do a full scan (to my knowledge), it is realtime. Overall Watchdog worked well for our use case although it only makes sense when you really have too much data to manually scan and need a cross platform approach. – SilentSteel Feb 24 '21 at 20:25
103

If polling is good enough for you, I'd just watch if the "modified time" file stat changes. To read it:

os.stat(filename).st_mtime

(Also note that the Windows native change event solution does not work in all circumstances, e.g. on network drives.)

import os

class Monkey(object):
    def __init__(self):
        self._cached_stamp = 0
        self.filename = '/path/to/file'

    def ook(self):
        stamp = os.stat(self.filename).st_mtime
        if stamp != self._cached_stamp:
            self._cached_stamp = stamp
            # File has changed, so do something...
Deestan
  • 15,019
  • 4
  • 28
  • 48
  • 2
    How can you do this on an interval? – dopatraman May 04 '16 at 15:16
  • 2
    @dopatraman Here is how you can do this on an interval ` import sys import time pub = Monkey() while True: try: time.sleep(1) pub.watch() except KeyboardInterrupt: print('\nDone') break except: print(f'Unhandled error: {sys.exc_info()[0]}') ` – Vlad Bezden Apr 25 '17 at 19:22
  • 1
    Great simple solution! I added a check to keep it from reporting the file changed on first run: `if self._cached_stamp is not None`. – Noumenon Feb 27 '20 at 16:28
  • there's no `watch` method as @VladBezden wrote. is code missing? – Lei Yang Jan 22 '21 at 08:15
81

Have you already looked at the documentation available on http://timgolden.me.uk/python/win32_how_do_i/watch_directory_for_changes.html? If you only need it to work under Windows the 2nd example seems to be exactly what you want (if you exchange the path of the directory with one of the files you want to watch).

Otherwise, polling will probably be the only really platform-independent option.

Note: I haven't tried any of these solutions.

rakeb.mazharul
  • 5,423
  • 3
  • 18
  • 39
Horst Gutmann
  • 9,226
  • 1
  • 24
  • 30
  • 6
    This answer is Windows-specific, but it appears that some cross-platform solutions to this problem have been posted here as well. – Anderson Green Aug 13 '13 at 22:53
  • Is there a benchmark, if this process is slower then implementing it in a native language like c++ ? – user1767754 Nov 03 '14 at 14:13
  • It's better to insert the relevant content from cited sources as they can become outdated. – Trilarion Apr 28 '17 at 11:19
  • 3
    (1.) at the end of this answer is a strong disclaimer... "I haven't tried any of these solutions". (2.) this answer is more or less a ["link only" answer](https://meta.stackexchange.com/questions/8231/are-answers-that-just-contain-links-elsewhere-really-good-answers) (3.) the answer mentions "polling" but does not provide add anything helpful after that ... where as [@Deestan's answer](http://stackoverflow.com/a/182259/52074) does provide some good info on polling – Trevor Boyd Smith May 01 '17 at 14:28
51

If you want a multiplatform solution, then check QFileSystemWatcher. Here an example code (not sanitized):

from PyQt4 import QtCore

@QtCore.pyqtSlot(str)
def directory_changed(path):
    print('Directory Changed!!!')

@QtCore.pyqtSlot(str)
def file_changed(path):
    print('File Changed!!!')

fs_watcher = QtCore.QFileSystemWatcher(['/path/to/files_1', '/path/to/files_2', '/path/to/files_3'])

fs_watcher.connect(fs_watcher, QtCore.SIGNAL('directoryChanged(QString)'), directory_changed)
fs_watcher.connect(fs_watcher, QtCore.SIGNAL('fileChanged(QString)'), file_changed)
ssc
  • 8,576
  • 8
  • 51
  • 84
hipersayan_x
  • 511
  • 4
  • 3
  • 7
    I think that this is quite possibly the best answer of the bunch given that they either a) rely on Win32's FileSystemwatcher object and cannot be ported or b) poll for the file (which is bad for performance and will not scale). It's a pity Python doesn't have this facility built in as PyQt is a huge dependency if all you're using is teh QFileSystemWatcher class. – CadentOrange Oct 13 '11 at 10:07
  • 4
    I like this solution. I wanted to point out that you'll need a QApplication instance for it to work, I added "app = QtGui.QApplication(sys.argv)" right under the imports and then "app.exec_()" after the signal connections. – spencewah May 02 '12 at 22:36
  • Just testing this on a Linux box, I'm seeing that the directory_changed method is being called, but not file_changed. – Ken Kinder Nov 22 '12 at 19:08
  • @CadentOrange, if you don't like the pyQt dependency, [the `watchdog` package is the right answer](http://stackoverflow.com/a/4690739/667301) – Mike Pennington Mar 27 '13 at 17:28
  • why not use `PySide` for that instead of `PyQt` for such a small use. – Ciasto piekarz Sep 16 '15 at 15:22
  • Just wanted to add this works on Windows network locations using mapped drive letters or the full UNC path. – chip Aug 04 '16 at 18:01
  • This is no longer supported on PyQt5 – Romain Vincent Feb 11 '20 at 14:08
29

It should not work on windows (maybe with cygwin ?), but for unix user, you should use the "fcntl" system call. Here is an example in Python. It's mostly the same code if you need to write it in C (same function names)

import time
import fcntl
import os
import signal

FNAME = "/HOME/TOTO/FILETOWATCH"

def handler(signum, frame):
    print "File %s modified" % (FNAME,)

signal.signal(signal.SIGIO, handler)
fd = os.open(FNAME,  os.O_RDONLY)
fcntl.fcntl(fd, fcntl.F_SETSIG, 0)
fcntl.fcntl(fd, fcntl.F_NOTIFY,
            fcntl.DN_MODIFY | fcntl.DN_CREATE | fcntl.DN_MULTISHOT)

while True:
    time.sleep(10000)
Maxime
  • 1,918
  • 1
  • 26
  • 38
  • 3
    Works like a charm with Linux kernel 2.6.31 on an ext4 file system (on Ubuntu 10.04), though only for directories - it raises an IOError "not a directory" if I use it with a file. – David Underhill Apr 30 '10 at 00:44
  • 1
    GREAT! Same for me, works for directory only and watch files in this directory. But it won't work for modified files in subdirectories, so it looks like you need to walk throught subdirectories and watch all of them. (or is there a better way to do this?) – lfagundes Nov 08 '10 at 10:15
20

Check out pyinotify.

inotify replaces dnotify (from an earlier answer) in newer linuxes and allows file-level rather than directory-level monitoring.

aljabear
  • 5,832
  • 5
  • 40
  • 69
Michael Palmer
  • 201
  • 2
  • 2
  • 5
    Not to put a damper on this answer, but after reading this article, I would say that it may not be as glamourous a solution as thought. http://www.serpentine.com/blog/2008/01/04/why-you-should-not-use-pyinotify/ – NuclearPeon Nov 22 '14 at 04:53
  • 1
    pyinotify has a lot of disadvantages starting from very unpythonic code base to memory consumption. Better to look for other options.. – NightOwl19 Aug 22 '18 at 09:31
13

Well after a bit of hacking of Tim Golden's script, I have the following which seems to work quite well:

import os

import win32file
import win32con

path_to_watch = "." # look at the current directory
file_to_watch = "test.txt" # look for changes to a file called test.txt

def ProcessNewData( newData ):
    print "Text added: %s"%newData

# Set up the bits we'll need for output
ACTIONS = {
  1 : "Created",
  2 : "Deleted",
  3 : "Updated",
  4 : "Renamed from something",
  5 : "Renamed to something"
}
FILE_LIST_DIRECTORY = 0x0001
hDir = win32file.CreateFile (
  path_to_watch,
  FILE_LIST_DIRECTORY,
  win32con.FILE_SHARE_READ | win32con.FILE_SHARE_WRITE,
  None,
  win32con.OPEN_EXISTING,
  win32con.FILE_FLAG_BACKUP_SEMANTICS,
  None
)

# Open the file we're interested in
a = open(file_to_watch, "r")

# Throw away any exising log data
a.read()

# Wait for new data and call ProcessNewData for each new chunk that's written
while 1:
  # Wait for a change to occur
  results = win32file.ReadDirectoryChangesW (
    hDir,
    1024,
    False,
    win32con.FILE_NOTIFY_CHANGE_LAST_WRITE,
    None,
    None
  )

  # For each change, check to see if it's updating the file we're interested in
  for action, file in results:
    full_filename = os.path.join (path_to_watch, file)
    #print file, ACTIONS.get (action, "Unknown")
    if file == file_to_watch:
        newText = a.read()
        if newText != "":
            ProcessNewData( newText )

It could probably do with a load more error checking, but for simply watching a log file and doing some processing on it before spitting it out to the screen, this works well.

Thanks everyone for your input - great stuff!

Jon Cage
  • 33,172
  • 32
  • 120
  • 206
12

For watching a single file with polling, and minimal dependencies, here is a fully fleshed-out example, based on answer from Deestan (above):

import os
import sys 
import time

class Watcher(object):
    running = True
    refresh_delay_secs = 1

    # Constructor
    def __init__(self, watch_file, call_func_on_change=None, *args, **kwargs):
        self._cached_stamp = 0
        self.filename = watch_file
        self.call_func_on_change = call_func_on_change
        self.args = args
        self.kwargs = kwargs

    # Look for changes
    def look(self):
        stamp = os.stat(self.filename).st_mtime
        if stamp != self._cached_stamp:
            self._cached_stamp = stamp
            # File has changed, so do something...
            print('File changed')
            if self.call_func_on_change is not None:
                self.call_func_on_change(*self.args, **self.kwargs)

    # Keep watching in a loop        
    def watch(self):
        while self.running: 
            try: 
                # Look for changes
                time.sleep(self.refresh_delay_secs) 
                self.look() 
            except KeyboardInterrupt: 
                print('\nDone') 
                break 
            except FileNotFoundError:
                # Action on file not found
                pass
            except: 
                print('Unhandled error: %s' % sys.exc_info()[0])

# Call this function each time a change happens
def custom_action(text):
    print(text)

watch_file = 'my_file.txt'

# watcher = Watcher(watch_file)  # simple
watcher = Watcher(watch_file, custom_action, text='yes, changed')  # also call custom action function
watcher.watch()  # start the watch going
4Oh4
  • 1,549
  • 1
  • 13
  • 29
  • 2
    You could make `watch_file` and `_cached_stamp` into lists, and iterate across them in a for loop. Doesn't really scale well to large numbers of files though – 4Oh4 Mar 05 '18 at 15:56
  • Doesn't this trigger the action every time it's run? _cached_stamp is set to 0 and then compared to os.stat(self.filename).st_mtime. _cached_stamp should be set to os.stat(self.filename).st_mtime in the constructor, no? – Seanonymous Aug 16 '19 at 22:08
  • 1
    `call_func_on_change()` will be triggered on first run of `look()`, but then `_cached_stamp` is updated, so won't be triggered again until the value of `os.stat(self.filename).st_mtime. _cached_stamp` changes. – 4Oh4 Aug 18 '19 at 13:20
  • 1
    You could set the value of `_cached_stamp` in the constructor if you didn't want `call_func_on_change()` to be called on first run – 4Oh4 Aug 18 '19 at 13:21
  • I've used your script to call some function on file change. My function doesn't take any arguments unlike yours. I thought that to make it work I need to remove *args, **kwargs It looked that (I put only lines with changes): `self.call_func_on_change(self) def custom_action(): watcher = Watcher(watch_file, custom_action())` But this did not work. Action was only called during first iteration: File changed yes, changed File changed File changed File changed It started working when I kept *args and call it: `watcher = Watcher(watch_file, custom_action)` I struggle to wonder why? – zwornik Apr 01 '20 at 14:10
  • Hard to debug incomplete code in a comment - maybe post a new question – 4Oh4 Apr 02 '20 at 18:51
8

Check my answer to a similar question. You could try the same loop in Python. This page suggests:

import time

while 1:
    where = file.tell()
    line = file.readline()
    if not line:
        time.sleep(1)
        file.seek(where)
    else:
        print line, # already has newline

Also see the question tail() a file with Python.

Community
  • 1
  • 1
Bruno De Fraine
  • 39,825
  • 8
  • 50
  • 62
  • You can you sys.stdout.write(line). You code doesn't work if the file is truncated. Python has builtin function file(). – jfs Oct 08 '08 at 13:04
  • I've posted a modified version of your code. You may incorporate it in your answer if it works for you. – jfs Oct 08 '08 at 13:15
7

This is another modification of Tim Goldan's script that runs on unix types and adds a simple watcher for file modification by using a dict (file=>time).

usage: whateverName.py path_to_dir_to_watch

#!/usr/bin/env python

import os, sys, time

def files_to_timestamp(path):
    files = [os.path.join(path, f) for f in os.listdir(path)]
    return dict ([(f, os.path.getmtime(f)) for f in files])

if __name__ == "__main__":

    path_to_watch = sys.argv[1]
    print('Watching {}..'.format(path_to_watch))

    before = files_to_timestamp(path_to_watch)

    while 1:
        time.sleep (2)
        after = files_to_timestamp(path_to_watch)

        added = [f for f in after.keys() if not f in before.keys()]
        removed = [f for f in before.keys() if not f in after.keys()]
        modified = []

        for f in before.keys():
            if not f in removed:
                if os.path.getmtime(f) != before.get(f):
                    modified.append(f)

        if added: print('Added: {}'.format(', '.join(added)))
        if removed: print('Removed: {}'.format(', '.join(removed)))
        if modified: print('Modified: {}'.format(', '.join(modified)))

        before = after
ronedg
  • 1,040
  • 9
  • 12
7

Simplest solution for me is using watchdog's tool watchmedo

From https://pypi.python.org/pypi/watchdog I now have a process that looks up the sql files in a directory and executes them if necessary.

watchmedo shell-command \
--patterns="*.sql" \
--recursive \
--command='~/Desktop/load_files_into_mysql_database.sh' \
.
redestructa
  • 1,135
  • 1
  • 11
  • 11
6

Well, since you are using Python, you can just open a file and keep reading lines from it.

f = open('file.log')

If the line read is not empty, you process it.

line = f.readline()
if line:
    // Do what you want with the line

You may be missing that it is ok to keep calling readline at the EOF. It will just keep returning an empty string in this case. And when something is appended to the log file, the reading will continue from where it stopped, as you need.

If you are looking for a solution that uses events, or a particular library, please specify this in your question. Otherwise, I think this solution is just fine.

seuvitor
  • 281
  • 1
  • 2
  • 10
6

Here is a simplified version of Kender's code that appears to do the same trick and does not import the entire file:

# Check file for new data.

import time

f = open(r'c:\temp\test.txt', 'r')

while True:

    line = f.readline()
    if not line:
        time.sleep(1)
        print 'Nothing New'
    else:
        print 'Call Function: ', line
SilentGhost
  • 264,945
  • 58
  • 291
  • 279
AlaXul
  • 61
  • 1
  • 1
4

As you can see in Tim Golden's article, pointed by Horst Gutmann, WIN32 is relatively complex and watches directories, not a single file.

I'd like to suggest you look into IronPython, which is a .NET python implementation. With IronPython you can use all the .NET functionality - including

System.IO.FileSystemWatcher

Which handles single files with a simple Event interface.

Community
  • 1
  • 1
gimel
  • 73,814
  • 10
  • 69
  • 104
  • @Ciasto because then you have to have Iron Python available rather than a basic Python installation. – Jon Cage Sep 17 '15 at 13:05
2
ACTIONS = {
  1 : "Created",
  2 : "Deleted",
  3 : "Updated",
  4 : "Renamed from something",
  5 : "Renamed to something"
}
FILE_LIST_DIRECTORY = 0x0001

class myThread (threading.Thread):
    def __init__(self, threadID, fileName, directory, origin):
        threading.Thread.__init__(self)
        self.threadID = threadID
        self.fileName = fileName
        self.daemon = True
        self.dir = directory
        self.originalFile = origin
    def run(self):
        startMonitor(self.fileName, self.dir, self.originalFile)

def startMonitor(fileMonitoring,dirPath,originalFile):
    hDir = win32file.CreateFile (
        dirPath,
        FILE_LIST_DIRECTORY,
        win32con.FILE_SHARE_READ | win32con.FILE_SHARE_WRITE,
        None,
        win32con.OPEN_EXISTING,
        win32con.FILE_FLAG_BACKUP_SEMANTICS,
        None
    )
    # Wait for new data and call ProcessNewData for each new chunk that's
    # written
    while 1:
        # Wait for a change to occur
        results = win32file.ReadDirectoryChangesW (
            hDir,
            1024,
            False,
            win32con.FILE_NOTIFY_CHANGE_LAST_WRITE,
            None,
            None
        )
        # For each change, check to see if it's updating the file we're
        # interested in
        for action, file_M in results:
            full_filename = os.path.join (dirPath, file_M)
            #print file, ACTIONS.get (action, "Unknown")
            if len(full_filename) == len(fileMonitoring) and action == 3:
                #copy to main file
                ...
martineau
  • 99,260
  • 22
  • 139
  • 249
imp
  • 1,575
  • 1
  • 24
  • 38
2

This is an example of checking a file for changes. One that may not be the best way of doing it, but it sure is a short way.

Handy tool for restarting application when changes have been made to the source. I made this when playing with pygame so I can see effects take place immediately after file save.

When used in pygame make sure the stuff in the 'while' loop is placed in your game loop aka update or whatever. Otherwise your application will get stuck in an infinite loop and you will not see your game updating.

file_size_stored = os.stat('neuron.py').st_size

  while True:
    try:
      file_size_current = os.stat('neuron.py').st_size
      if file_size_stored != file_size_current:
        restart_program()
    except: 
      pass

In case you wanted the restart code which I found on the web. Here it is. (Not relevant to the question, though it could come in handy)

def restart_program(): #restart application
    python = sys.executable
    os.execl(python, python, * sys.argv)

Have fun making electrons do what you want them to do.

bas080
  • 133
  • 1
  • 7
  • Seems like using `.st_mtime` instead of `.st_size` would be more reliable and an equally short way of doing this, although the OP has indicated that he didn't want to do it via polling. – martineau Jun 29 '15 at 16:40
1

Here's an example geared toward watching input files that write no more than one line per second but usually a lot less. The goal is to append the last line (most recent write) to the specified output file. I've copied this from one of my projects and just deleted all the irrelevant lines. You'll have to fill in or change the missing symbols.

from PyQt5.QtCore import QFileSystemWatcher, QSettings, QThread
from ui_main_window import Ui_MainWindow   # Qt Creator gen'd 

class MainWindow(QMainWindow, Ui_MainWindow):
    def __init__(self, parent=None):
        QMainWindow.__init__(self, parent)
        Ui_MainWindow.__init__(self)
        self._fileWatcher = QFileSystemWatcher()
        self._fileWatcher.fileChanged.connect(self.fileChanged)

    def fileChanged(self, filepath):
        QThread.msleep(300)    # Reqd on some machines, give chance for write to complete
        # ^^ About to test this, may need more sophisticated solution
        with open(filepath) as file:
            lastLine = list(file)[-1]
        destPath = self._filemap[filepath]['dest file']
        with open(destPath, 'a') as out_file:               # a= append
            out_file.writelines([lastLine])

Of course, the encompassing QMainWindow class is not strictly required, ie. you can use QFileSystemWatcher alone.

1

Seems that no one has posted fswatch. It is a cross-platform file system watcher. Just install it, run it and follow the prompts.

I've used it with python and golang programs and it just works.

Gus
  • 4,483
  • 4
  • 26
  • 29
1

Since I have it installed globally, my favorite approach is to use nodemon. If your source code is in src, and your entry point is src/app.py, then it's as easy as:

nodemon -w 'src/**' -e py,html --exec python src/app.py

... where -e py,html lets you control what file types to watch for changes.

Magnus
  • 1,284
  • 1
  • 12
  • 30
0

The best and simplest solution is to use pygtail: https://pypi.python.org/pypi/pygtail

from pygtail import Pygtail
import sys

while True:
    for line in Pygtail("some.log"):
        sys.stdout.write(line)
Rob Kwasowski
  • 2,415
  • 2
  • 7
  • 28
george
  • 1,412
  • 15
  • 16
0

You can also use a simple library called repyt, here is an example:

repyt ./app.py
Rafal Enden
  • 2,317
  • 1
  • 16
  • 13
0

related @4Oh4 solution a smooth change for a list of files to watch;

import os
import sys
import time

class Watcher(object):
    running = True
    refresh_delay_secs = 1

    # Constructor
    def __init__(self, watch_files, call_func_on_change=None, *args, **kwargs):
        self._cached_stamp = 0
        self._cached_stamp_files = {}
        self.filenames = watch_files
        self.call_func_on_change = call_func_on_change
        self.args = args
        self.kwargs = kwargs

    # Look for changes
    def look(self):
        for file in self.filenames:
            stamp = os.stat(file).st_mtime
            if not file in self._cached_stamp_files:
                self._cached_stamp_files[file] = 0
            if stamp != self._cached_stamp_files[file]:
                self._cached_stamp_files[file] = stamp
                # File has changed, so do something...
                file_to_read = open(file, 'r')
                value = file_to_read.read()
                print("value from file", value)
                file_to_read.seek(0)
                if self.call_func_on_change is not None:
                    self.call_func_on_change(*self.args, **self.kwargs)

    # Keep watching in a loop
    def watch(self):
        while self.running:
            try:
                # Look for changes
                time.sleep(self.refresh_delay_secs)
                self.look()
            except KeyboardInterrupt:
                print('\nDone')
                break
            except FileNotFoundError:
                # Action on file not found
                pass
            except Exception as e:
                print(e)
                print('Unhandled error: %s' % sys.exc_info()[0])

# Call this function each time a change happens
def custom_action(text):
    print(text)
    # pass

watch_files = ['/Users/mexekanez/my_file.txt', '/Users/mexekanez/my_file1.txt']

# watcher = Watcher(watch_file)  # simple



if __name__ == "__main__":
    watcher = Watcher(watch_files, custom_action, text='yes, changed')  # also call custom action function
    watcher.watch()  # start the watch going
mexekanez
  • 186
  • 1
  • 5
0
import inotify.adapters
from datetime import datetime


LOG_FILE='/var/log/mysql/server_audit.log'


def main():
    start_time = datetime.now()
    while True:
        i = inotify.adapters.Inotify()
        i.add_watch(LOG_FILE)
        for event in i.event_gen(yield_nones=False):
            break
        del i

        with open(LOG_FILE, 'r') as f:
            for line in f:
                entry = line.split(',')
                entry_time = datetime.strptime(entry[0],
                                               '%Y%m%d %H:%M:%S')
                if entry_time > start_time:
                    start_time = entry_time
                    print(entry)


if __name__ == '__main__':
    main()
Rafael Beirigo
  • 820
  • 6
  • 10
-1

If you're using windows, create this POLL.CMD file

@echo off
:top
xcopy /m /y %1 %2 | find /v "File(s) copied"
timeout /T 1 > nul
goto :top

then you can type "poll dir1 dir2" and it will copy all the files from dir1 to dir2 and check for updates once per second.

The "find" is optional, just to make the console less noisy.

This is not recursive. Maybe you could make it recursive using /e on the xcopy.

John Henckel
  • 7,448
  • 2
  • 55
  • 68
-3

I don't know any Windows specific function. You could try getting the MD5 hash of the file every second/minute/hour (depends on how fast you need it) and compare it to the last hash. When it differs you know the file has been changed and you read out the newest lines.

scable
  • 4,054
  • 1
  • 24
  • 41
-6

I'd try something like this.

    try:
            f = open(filePath)
    except IOError:
            print "No such file: %s" % filePath
            raw_input("Press Enter to close window")
    try:
            lines = f.readlines()
            while True:
                    line = f.readline()
                    try:
                            if not line:
                                    time.sleep(1)
                            else:
                                    functionThatAnalisesTheLine(line)
                    except Exception, e:
                            # handle the exception somehow (for example, log the trace) and raise the same exception again
                            raw_input("Press Enter to close window")
                            raise e
    finally:
            f.close()

The loop checks if there is a new line(s) since last time file was read - if there is, it's read and passed to the functionThatAnalisesTheLine function. If not, script waits 1 second and retries the process.

kender
  • 79,300
  • 24
  • 99
  • 144
  • 4
    -1: Opening the file and reading lines isn't a great idea when the files could be 100's of MB big. You'd have to run it for each and every file too which would be bad when you want to watch 1000's of files. – Jon Cage Aug 04 '09 at 08:48
  • 1
    Really? Opening the file for changes? – Farshid Ashouri Sep 15 '14 at 21:05