Examples of who might find this useful:
- People who want to backup a directory every time a file in that directory is modified
- Groups of people who want to share a directory and need the ownership and permission of all files set so everyone can share the files.
- Programmers who want their source code recompiled every time a change is made
- LaTeX authors who want their .tex file(s) processed every time a change is made
- Photographers who want to drop images into a directory and have them automatically resized
- YouTube users who want the videos they watch to be automatically saved to a directory
Summary:
dirmon.py is a small python script meant to run in the background or at startup, which can monitor a directory (and all its subdirectories) for changes. It can be configured to run arbitrary commands every time a file or subdirectory is created or modified.
There are three components to setting up dirmon: the dirmon.py program, a configuration file (~/.dirmon), and a script to be run whenever a file is created or modified.
Below you will find Instructions on how to setup each of these components.
Following the Instructions is an Examples section which shows configuration files and scripts for each of the situations listed above.
The instructions:
- Install the python-pyinotify package (~260 KiB)
Code:sudo apt-get install python-pyinotify- Save this to ~/bin/dirmon.py.
PHP Code:
#!/usr/bin/env python
import os
import optparse
from pprint import pformat
from pyinotify import (WatchManager, Notifier, ProcessEvent,
ALL_EVENTS,IN_MOVED_TO,IN_MOVED_FROM,IN_DELETE,IN_MODIFY,
IN_CREATE)
from subprocess import Popen,PIPE,STDOUT
import signal
import re
__author__ = 'unutbu'
__version__ = '$Revision: 0.2 $'
__date__ = '$Date: 2009-03-23 $'
__notes__='''
This causes dirmon.py to reread the configuration file:
kill -SIGUSR1 $(ps axuw | awk '/[d]irmon/{print $2}')
'''
__usage__='''
dirmon.py
'''
class MyProcessEvent(ProcessEvent):
def __init__(self,config):
self.config=config
self.pat=re.compile(config['filter'])
def path(self,event):
return event.name and os.path.join(event.path, event.name) or event.path
def cmd(self,event):
path=self.path(event)
cmd=self.config['command'].replace('{}',path)
return cmd
def process_default(self,event):
path=self.path(event)
if self.pat.search(path):
proc=Popen(self.cmd(event), shell=True, stdout=PIPE, )
output=proc.communicate()[0]
print output,
class ConfigProcessEvent(ProcessEvent):
def __init__(self,main):
self.main=main
def process_default(self,event):
outfile=os.path.join(self.main.opt.config_file+'.out')
fh=open(outfile,'w')
fh.write('Rereading %s'%self.main.opt.config_file)
fh.close()
self.main.read_config_file()
self.main.setup_watch_manager()
class Main(object):
def parse_options(self):
usage = 'usage: %prog [options]'
parser = optparse.OptionParser(usage=usage)
parser.add_option('-c', '--config-file', dest='config_file',
default=os.path.join(os.environ['HOME'],'.dirmon/dirmonrc'),
help='specify config-file. Default=~/.dirmon',
metavar='FILE')
parser.add_option('-v', '--verbose', dest='verbose',
action='store_true',
default=False,
help="print messages to stdout")
(self.opt, self.args) = parser.parse_args()
def read_config_file(self):
if os.path.exists(self.opt.config_file):
self.config_dir,filename=os.path.split(self.opt.config_file)
fh=open(self.opt.config_file,'r')
self.stanzas=[]
config={}
for line in fh:
if line.startswith('#') or len(line.strip())==0:
continue
elif line.startswith('path:'):
config['path']=os.path.expanduser(line.replace('path:','').strip())
#
# If the path does not end with a slash (/), the WatchManager
# does not recursively monitory subdirectories.
#
if os.path.isdir(config['path']) and not config['path'].endswith('/'):
config['path']=config['path']+'/'
elif line.startswith('event:'):
config['event']=line.replace('event:','').strip()
elif line.startswith('command:'):
config['command']=os.path.expanduser(
line.replace('command:','').strip())
elif line.startswith('filter:'):
config['filter']=line.replace('filter:','').strip()
if (('path' in config) and ('event' in config) and
('filter' in config) and ('command' in config)):
self.stanzas.append(config)
config={}
if self.opt.verbose:
print(pformat(self.stanzas))
else:
print "Error: can't find config file %s"%self.opt.config_file
exit(1)
def setup_watch_manager(self):
wm = WatchManager()
self.notifier = Notifier(wm)
#
# Monitory your own config file (.dirmon)
#
mask = IN_MODIFY|IN_CREATE
wm.add_watch(self.config_dir, mask, proc_fun=ConfigProcessEvent(self))
for config in self.stanzas:
if config['event']=='file_created':
mask = IN_CREATE
wm.add_watch(config['path'], mask, rec=True, auto_add=True,
proc_fun=MyProcessEvent(config))
elif config['event']=='file_modified':
mask = IN_MODIFY|IN_CREATE
wm.add_watch(config['path'], mask, rec=True, auto_add=True,
proc_fun=MyProcessEvent(config))
elif config['event']=='dir_changed':
mask = (IN_MODIFY|IN_CREATE|
IN_DELETE|IN_MOVED_FROM|
IN_MOVED_TO)
wm.add_watch(config['path'], mask, rec=True, auto_add=True,
proc_fun=MyProcessEvent(config))
elif config['event']=='paranoid':
mask = ALL_EVENTS
wm.add_watch(config['path'], mask, rec=True, auto_add=True,
proc_fun=ProcessEvent())
def sigusr_handler(self,signal, frame):
raise UserWarning
def setup_sigusr_handler(self):
signal.signal(signal.SIGUSR1, self.sigusr_handler)
def loop(self):
while True:
try:
self.notifier.process_events()
if self.notifier.check_events():
self.notifier.read_events()
except KeyboardInterrupt:
self.notifier.stop()
break
except UserWarning:
self.read_config_file()
self.setup_watch_manager()
def __init__(self):
self.parse_options()
self.read_config_file()
self.setup_watch_manager()
self.setup_sigusr_handler()
self.loop()
if __name__=='__main__':
Main()
- Make the program executable:
Code:chmod 755 ~/bin/dirmon.py- dirmon.py is going to monitor a directory. Each time a file changes in that directory, a script is going to be run. For example, save this script in ~/bin/dirmon-script.sh
You can put any commands that you wish here.PHP Code:
#!/bin/sh
echo "File modified: $1"
"$1" will hold the name of the file that has changed.
Make the script executable:
Code:chmod 755 ~/bin/dirmon-script.sh- Next we make a configuration file. Using a text editor, save the following in ~/.dirmon.
Change /path/to/directory to the directory you wish to monitor.Code:path: /path/to/directory event: file_modified command: ~/bin/dirmon-script.sh filter: regexp
Change ~/bin/dirmon-script.sh to the path to your script.
regexp can be any (python) regular expression. Only files whose name matches the regexp pattern will activate the script. If you want all files in the directory to activate the script (when a change is made), you can use a single period (.) as your regexp.- (You can skip this section the first time through and come back to this once you get a taste for what's going on...)
Fun facts about the configuration file .dirmon:
- In the command specified after the "command:" keyword, every occurrence of paired-braces {} will be replaced by the name of the file which triggered dirmon.py.
- The regexp is matched against a full path name, not just a filename. Keep this in mind if you use ^ to match the beginning of a string. You will be matching the beginning of the full path, not the beginning of the filename.
- WARNING: If your script writes output into the monitored directory, you must make sure the filter pattern excludes that output, lest dirmon.py end up in an infinite loop... highly undesirable.
- Collectively, a path,event,command and filter statement comprise a stanza. You can monitor more than one directory simply by writing a stanza for each directory you wish to monitor. No two stanzas can have the same path. If you do, only the last such stanza will have an effect. If you run into this situation, simply modify your script to contain the commands from both stanzas.
- Lines that begin with # or which contain only spaces are ignored
- If you want a command to be run only when a file is created (rather than when it is modified), use
When you useCode:event: file_created
the command will be run whenever a file is created or modified.Code:event: file_modified
- To monitor the directory, run
Fun facts about dirmon.py:Code:dirmon.py &
- dirmon.py reads the configuration file ~/.dirmon by default. If you wish it to read a different configuration file, run
Code:dirmon.py --config-file /path/to/config/file- You can ask dirmon.py to reread the configuration file after it has been started.
Just edit ~/.dirmon and then send dirmon.py a SIGUSR1 signal:
Code:kill -SIGUSR1 $(ps axuw | awk '/[d]irmon/{print $2}')- If you run
It will print out a list of configuration stanzas that it is currently using.Code:dirmon.py --verbose
This might be useful for debugging problems.
Examples:
- For people who want to backup a directory:
Place in ~/bin/dirmon-script.sh:
Place in ~/.dirmon:PHP Code:
#!/bin/sh
rsync -a --delete /path/to/source/ /path/to/target/
Code:path: /path/to/source/ event: dir_changed command: ~/bin/dirmon-script.sh filter: .- For groups of people who want to share a directory:
Place in ~/bin/dirmon-script.sh:
Place in ~/.dirmon:PHP Code:
#!/bin/sh
dir=$(dirname "$1")
chown users:users -R "$dir"
chmod g+rw "$1"
Code:path: /path/to/shared/directory event: file_modified command: ~/bin/dirmon-script.sh {} filter: .- For programmers who want their source code recompiled every time a change is made:
Place in ~/bin/dirmon-script.sh:
Place in ~/.dirmon:PHP Code:
#!/bin/sh
dir=/path/to/project/source
cd "$dir"
make
Code:path: /path/to/project/source event: file_modified command: ~/bin/dirmon-script.sh filter: \.c$- LaTeX authors who want their .tex file(s) processed every time a change is made
Place in ~/bin/dirmon-script.sh:
Place in ~/.dirmon:PHP Code:
#!/bin/sh
latex $1
Code:path: /path/to/document/ event: file_modified command: ~/bin/dirmon-script.sh {} filter: \.tex$- Photographers who want to drop images into ~/images_in and have them resized to 800x600 and placed in ~/images_out:
Install the imagemagick package:
Place in ~/bin/dirmon-script.py:Code:sudo apt-get install imagemagick
Place in ~/.dirmon:PHP Code:
#!/usr/bin/env python
import os
import sys
target_dir=os.path.join(os.environ['HOME'],'images_out')
try:
os.makedirs(target_dir)
except OSError:
pass
source=sys.argv[1]
filename=os.path.split(source)[1]
target=os.path.join(target_dir,filename)
cmd='convert -resize 800x600! %s %s'%(source,target)
print cmd
os.system(cmd)
# os.unlink(source) # Uncomment this line if you wish to delete files in ~/images_in after being resized.
Code:path: ~/images_in event: file_modified command: ~/bin/dirmon-script.py {} filter: \.tex$- YouTube users who want every video they watch to be automatically saved to ~/Videos
Place in ~/bin/dirmon-script.sh:
Place in ~/.dirmon:PHP Code:
#!/bin/sh
rsync -a /tmp/Flash* ~/Videos
Code:path: /tmp event: file_modified command: ~/bin/dirmon-script.sh filter: Flash
I hope you have as much fun using dirmon as I had writing it. Enjoy.
Bookmarks