12

I'm looking for a CLI tool which will watch a directory and spit out the names of files that change in real time.

some_watch_command /path/to/some/folder | xargs some_callback

I'm aware of inotify (inotify-tools?) and it seems to be what I need, but I need something that is both Linux (in my case Ubuntu) and OSX compatible.

It doesn't need to be lightning fast, but it does need to trigger upon changes (within a second is reasonable). Also, I don't necessarily need the exact CLI program mentioned above. If some underlying tech exists and is easily scriptable on both platforms that would be great too.

1

4 Answers 4

12

OS X would typically use Folder Actions or Launchd for this task.

The only cross-platform tool I know is the watchdog API for Python (thus available for OS X and Linux). Install it through pip (or easy_install)

pip install watchdog

It comes with the watchmedo command line tool, which allows you to run shell commands on system events. Check its general syntax with watchmedo --help

Here is a short example, where you could easily modify the command and the path highlighted in bold:

watchmedo shell-command \
    --recursive \
    --command='echo "${watch_src_path}"' \
    /some/folder

This would simply spit out the paths to all changed files or folders, allowing you to pipe watchmedo's output to another shell command.

1
  • Looks like Folder Actions have some limitations (and are hard to test without OSX), but watchmedo is working perfectly for me. Thanks! Commented Jun 4, 2012 at 23:53
2

I've tweaked some Ruby scripts I've come across to do exactly what you're looking for. Here's the Ruby code:

#!/usr/bin/ruby
# Inspired by http://vikhyat.net/code/snippets/#watchfile

# How to use:
# This script takes two paramaters:  a folder and a shell command
# The script watches for when files in the folder are changed.  When they are, the shell command executes
# Here are some shortcuts you can put in the shell script:
# %F = filename (with extension)
# %B = base filename (without extension)


unless ARGV.length == 2
  puts "\e[32m    Usage: \e[0mruby OnSaveFolder.rb 'folder' 'command'"
  exit
end

require 'digest/md5'
require 'ftools'

$md5 = ''
$directory = File.expand_path(ARGV[0])
$contents = `ls #{$directory}`
$contentsList = $contents.split("\n")
$fileList = []


Dir.chdir($directory)

for i in $contentsList
  if ( File.file?(File.expand_path(i)) == true)
    $fileList.push(i)
  end
end

$processes = []

def watch(file, timeout, &cb)
  $md5 = Digest::MD5.hexdigest(File.read(file))
  loop do
    sleep timeout
    if ( temp = Digest::MD5.hexdigest(File.read(file)) ) != $md5
      $md5 = temp
      cb.call(file)
    end
  end
end

puts "\e[31m Watching files in folder \e[34m #{$directory}"
puts "\e[32m    Press Control+C to stop \e[0m"

for i in $fileList
  pid = fork do
    $num = 0
    $filePath = File.expand_path(i)

    watch i, 1 do |file|
      puts "\n     #{i} saved"

      $command = ARGV[1].dup
      if ( ARGV[1].include?('%F') )
        $command = $command.gsub!('%F', i)
      end
      if ( ARGV[1].include?('%B') )
        $command = $command.gsub!('%B', File.basename(i, '.*'))
      end

      $output = `#{$command}`
      if ( $output != '')
        puts "\e[34m     #{$command}\e[31m output: \e[0m"
        puts $output
      end
      puts "\e[34m     #{$command}\e[31m finished \e[0m (#{$num}, #{Time.now})\n"
      $num += 1

    end
  end
  $processes.push(pid)
end

Process.wait(pid)

for i in $processes
  `kill #{i}`
end

I named this script "OnSaveFolder.rb". It takes two parameters: the folder you want to watch for changes, and the bash code you want to run when there's been a change. For example,

ruby OnSaveFolder.rb '/home/Movies' 'echo "A movie has been changed!"'

I hope this helps! I've found that ruby works very well for this type of thing, and it's installed on OS X by default.

3
  • Gave it a shot but a little hard to tweak without any Ruby background. Thanks anyway! Commented Jun 4, 2012 at 23:54
  • this is great. any idea how to make it recursively watch folders below ?
    – Nate Flink
    Commented Jul 10, 2013 at 19:35
  • If I don't care about the execution of the command on change, would this be multi-platform, or only unix? And is it actually real time? Some file watchers out there has a ridiculous delay (minutes).
    – Automatico
    Commented Dec 9, 2013 at 11:47
2

You could put lsof into a watch command and have it update every <s> seconds:

watch -n <s> lsof

and launch that with whatever filter, such as watching pid1, pid2, pid3 and ignoring pid4

lsof -p "pid1,pid2,pid3,^pid4"

If that's not enough, you could always write your own filters with grep.

For OS X, see this question's answers.

0

entr is nice. You can filter the exact files you want it to watch, like this.

find . -name '*.py' | entr python runTests.py

If you want it to only monitor certain files inside a directory, you can craft your find command so that it only returns the files you want to watch.

I find it easier to configure than fswatch.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .