challenge ファイル更新の監視

あるファイル名がfilenameという変数に入っているとします。 このファイルが更新されるたびに"modified!"と表示するプログラムを作ってください。

もしOSに依存する場合はそのOS名のタグを、 依存しない場合は「OS非依存」というタグをつけてください。 わからなければつけなくても構いません。

Posted feedbacks - Nested

Flatten Hidden
なんか根本的にわかってないかもですが・・
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
(use file.util)

(define filename "hoge.txt")

(define (modify-checker)
  (let loop ((lastmodified (file-mtime filename)))
    (sys-sleep 3)
    (if (= lastmodified (file-mtime filename))
        (loop lastmodified)
        (begin (print "modified!")
               (loop (file-mtime filename))))))

(modify-checker)
同じ方針ですが、modifyされているときのアクションは純粋に副作用だけなので(printするだけ)、

(if 条件 (loop) (begin (action) (loop)))

よりは

(unless 条件 (action))
(loop)

のように副作用だけくくり出してしてしまうのが好みですね 
(分岐が増えた場合でもloopは一箇所にまとめられるし)。たとえば:
1
2
3
4
5
6
7
(define (modify-checker)
  (let loop ((previous #f)
             (modified (file-mtime filename)))
    (when (and previous (not (= previous modified)))
      (print "modified"))
    (sys-sleep 3)
    (loop modified (file-mtime filename))))
おお、キレイ!
勉強になります。ありがとうございます。

あと自分のでは、8行目と11行目の間に更新されると"modified!"の出力が一回少なくなってしまいますね。
1
2
3
4
5
6
7
8
last_mtime = File.mtime(filename)
loop do
  unless (mtime = File.mtime(filename)) == last_mtime
    puts "modified!" 
    last_mtime = mtime
  end
  sleep(0.1)
end
POE::Wheel::FollowTail
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#!/usr/local/bin/perl

use strict;
use warnings;

use POE;
use POE::Wheel::FollowTail;

my $target_filename = shift;

if ( !defined $target_filename or $target_filename eq '' ) {
        print "$0 <filename>\n";
        exit 1;
}

POE::Session->create(
        inline_states => {
                _start => \&setup,
                handler_input => \&handler_input,
        },
        args => [ $target_filename ],
);

POE::Kernel->run;
exit;

sub setup
{
        my ( $heap, $target_filename ) = @_[HEAP, ARG0];
        $heap->{tail_wheel} = POE::Wheel::FollowTail->new(
                Filename => $target_filename,
                InputEvent => 'handler_input',
        );
        return;
}

sub handler_input
{
        print "modified!\n";
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
using System;
using System.IO;

class Program
{
  static void Main()
  {
    string filename = @"c:\test.txt";
    FileSystemWatcher fsw = new FileSystemWatcher(
      Path.GetDirectoryName(filename),
      Path.GetFileName(filename));
    fsw.Changed += delegate(object sender, FileSystemEventArgs e)
    {
      Console.WriteLine("modified!");
    };
    while (true)
      fsw.WaitForChanged(WatcherChangeTypes.All);
  }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
require 'event' # http://yagi.xrea.jp/2006/08/event.rb

filename = File.expand_path("touched.txt")

last_mtime = File.stat(filename).mtime
loop {
  Win32::Event::FindChangeNotification(File.dirname(filename))
  file_mtime = File.stat(filename).mtime
  if last_mtime != file_mtime
    puts "modified!"
    last_mtime = file_mtime
  else
    sleep 0.1
  end
}
mtime を見ないで済むよう書き換えました。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
require 'rubygems'
require 'win32/ipc' # http://rubyforge.org/projects/win32utils
require 'win32/changenotify' # http://rubyforge.org/projects/win32utils
include Win32

exit if ARGV == []
filename = File.expand_path(ARGV[0])

flags = ChangeNotify::FILE_NAME | ChangeNotify::LAST_WRITE | ChangeNotify::SIZE
cn = ChangeNotify.new(File.dirname(filename), false, flags)

loop {
  cn.wait(){|sa|
    sa.each {|st|
      puts "modified!" if st.file_name == File.basename(filename)
    }
  }
}
$ perl watch.pl /path/to/file 3 なら3秒おきに監視みたいな感じ。 モジュール使うと反則かな?w
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#!/usr/bin/perl

use strict;
use warnings;

use Carp::Clan qw(croak);
use File::Monitor;

my $filename = shift @ARGV;
my $duration = int(shift @ARGV || 5);

croak qq|No such file. ($filename)| unless (-e $filename);
croak qq|Invalid duration| unless ($duration >= 1);

my $monitor = File::Monitor->new;
$monitor->watch(
    $filename, 
    sub {
        my ($name, $event, $change) = @_;
        if ($change) {
            print "modified\n";
        }
    }
);

while (1) {
    $monitor->scan;
    sleep $duration;
}
モジュールの豊富さもPerlのパワーの一つだと思うのでいいんじゃないでしょうか。
POE持ち出してきた自分はもっと反則なので大丈夫だと思います:)
ファイルの存在チェックとタイムスタンプのチェックのみを1秒周期で行っています。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
import java.io.File;

public class Sample {
    static final String filename = "./check";
    static final long INTERVAL = 1000; // 1 sec
    static final String MESSAGE = "modified!";

    public static void main(String[] args) throws InterruptedException {
        File checkFile = new File(filename);
        long lastModified = checkFile.lastModified();
        while (true) {
            Thread.sleep(INTERVAL);
            long lm2 = checkFile.lastModified();
            if (lastModified != lm2) {
                System.out.println(MESSAGE);
                lastModified = lm2;
            }
        }
    }
}
Cc, Ciはそれぞれ const Cc = Components.classes; const Ci = Components.interfaces; されているとして、 var fw = new FileWatch("/home/zigorou/hoge.txt", 5); fw.watch(); で5秒おきにErrorConsoleに対して更新されてればmodified表示。 fw.unwatch()で監視止める。 でももっと奇麗に書き方ありそうな気がする。Observerがそもそもあったりして(ぇ
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
var FileWatch = function(filename, duration) {
  this.file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
  this.file.initWithPath(filename);
  this.duration = duration;
};

FileWatch.prototype = {
  iid: null,
  lastTime: null,
  watch: function() {
    this.lastTime = (new Date()).getTime();
    var self = this;
    this.iid = setInterval(function() { self.watchFile(); }, this.duration * 1000);
  },
  watchFile: function() {
    if (this.file.lastModifiedTime - this.lastTime > 0) {
      this.log("modified");
    }

    this.lastTime = (new Date()).getTime();
  },
  log: function(message) {
    Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService).logStringMessage(message);
  },
  unwatch: function() {
    clearInterval(this.iid);
  }
};
Squeak Smalltalk で。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
| directory filename lastModTime |
directory := FileDirectory default.
filename := 'test.txt'.
lastModTime := (directory entryAt: filename) modificationTime.
[  [  | modificationTime |
      modificationTime := (directory entryAt: filename) modificationTime.
      modificationTime = lastModTime ifFalse: [
         Transcript cr; show: 'modified!'.
         lastModTime := modificationTime].
      (Delay forSeconds: 3) wait
   ] repeat
] forkAt: Processor userBackgroundPriority
EmacsLisp です :-)
毎秒、更新時刻を比較します。
(setq filename "hoge") などとして、M-x watch-modification で起動してください。
変数 filename がセットされてないなら、ファイル名を聞いてくるので指定してください。
キャンセルするには、M-x watch-modification-cancel です。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
(defvar watch-modification-repeat-sec 1.0)
(defvar watch-modification-timer nil)
(defvar watch-modification-stack nil)
(defvar watch-modification-buffer "*watch*")

(defun watch-modification ()
  (interactive)
  (let ((file (or (and (boundp 'filename) filename)
		  (read-file-name "File: "))))
    (unless (string= file "")
      (watch-modification-cancel)
      (setq watch-modification-timer
	    (run-with-timer 1 watch-modification-repeat-sec
			    'watch-modification-function file)))))

(defun watch-modification-cancel ()
  (interactive)
  (when watch-modification-timer
    (cancel-timer watch-modification-timer))
  (setq watch-modification-timer nil
	watch-modification-stack nil))

(defun watch-modification-function (file)
  (let ((modified-time (nth 5 (file-attributes file))))
    (if (null watch-modification-stack)
        (push modified-time watch-modification-stack)
      (unless (equal modified-time (car watch-modification-stack))
	(push modified-time watch-modification-stack)
	(save-selected-window
	  (pop-to-buffer
	   (set-buffer (get-buffer-create watch-modification-buffer)))
	  (insert "modified!\n"))))))
自分も良くわかってませんが、とりあえず投稿しときます。:p
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
(use file.util)

(define filename "hoge.txt")

(define (watch-modification file since nsec)
  (let loop ((last-mtime since))
    (sys-nanosleep nsec)
    (let ((current (max (file-mtime file) last-mtime)))
      (unless (= current last-mtime) (print "modified!"))
      (loop current))))

(watch-modification filename (file-mtime filename) 500000000) ;0.5s
実は役に立たない基本形(笑
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#include <stdio.h>
#include <unistd.h>
#include <time.h>
#include <sys/stat.h>
#include <sys/errno.h>

int moniter(const char *filename, const useconds_t interval)
{
    struct stat s;
    time_t      last_modified = 0;
    int         result = 0;

    printf("monitering file : %s\n", filename);
    result = stat(filename, &s);
    if( result == 0 )
    {
        last_modified = s.st_mtime;
        do
        {
            printf("."); fflush(stdout);
            stat(filename, &s);
            if( last_modified < s.st_mtime )
            {
                printf(" modified!\n");
                last_modified = s.st_mtime;
            }
            usleep( interval );
        }while(1);
     }
    else
    {
        printf("error:%d\n", errno);
    }
    return 0;
}

int main(int argc, char *argv[])
{
    if( argc < 2 )
    {
        return 1;
    }
    if( *argv[1] == '\0' )
    {
        return 2;
    }

    // 無限ループ 監視間隔1.5秒
    moniter(argv[1], 1500);
    return 0;
}
つづいて現実的かなと思うもの。
シグナルよりもソケットの方がいいかも。
detachして放置してもよい?
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
#include <stdio.h>
#include <unistd.h>
#include <time.h>
#include <pthread.h>
#include <sys/stat.h>
#include <sys/errno.h>
#include <sys/signal.h>

static int       continued   = 0;
static pthread_t threads[20];

static void signal_handler(int signum)
{
    continued = 0;
}

void *moniter(void *arg)
{
    struct stat s;
    const char *filename      = arg;
    time_t      last_modified = 0;
    int         result        = 0;

    if( filename == NULL )
    {
        return NULL;
    }

    printf("monitering file : %s\n", filename);
    result = stat(filename, &s);
    if( result == 0 )
    {
        last_modified = s.st_mtime;
        while( continued != 0 )
        {
            printf("."); fflush(stdout);
            stat(filename, &s);
            if( last_modified < s.st_mtime )
            {
                printf(" modified! %s\n", filename);
                last_modified = s.st_mtime;
            }
            usleep( 8000 );
        }
     }
    else
    {
        printf("error:%d\n", errno);
    }
    printf("monitering end[%s]\n", filename);
    fflush(stdout);
    return NULL;
}

size_t setup(char *filename[], size_t nFiles)
{
    struct sigaction act;
    int              signum[] = {SIGINT};
    size_t n;
    int result = 0;

    // シグナルの設定
    for(size_t num=0; num<sizeof(signum)/sizeof(*signum); num++)
    {
        if( sigaction(signum[n], NULL, &act) == 0 )
        {
            act.sa_handler = (void (*)(int))signal_handler;
            act.sa_flags &= ~SA_SIGINFO ;
            result = sigaction(signum[n], &act, NULL);
        }
    }

    // スレッドの設定
    continued = 1;
    for(n=0; n<nFiles; n++)
    {
        if( n >= 20 ) break;
        result = pthread_create( &threads[n], NULL, moniter, (void *)filename[n]);
    }
    return n;
}

int main(int argc, char *argv[])
{
    size_t nThreads = 0;

    if( argc < 2 )
    {
        return 1;
    }
    if( *argv[1] == '\0' )
    {
        return 2;
    }
    nThreads = setup(argv+1, argc-1);
    for(int n = 0; n< nThreads; n++ )
    {
        pthread_join( threads[n], NULL ); // 終了待ち合わせ
    }
    printf("all threads end\n");
    return 0;
}
CLI版。 PHPでローカルファイルを監視することなんて、まず無いと思いますが一応。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
#!/usr/bin/php
<?php
$filename = $argv[1];
$second   = 1;
if ( file_exists($filename) ) {
	$fp = fopen($filename,"r");
	$fbin1 = fread($fp,filesize($filename));
	fclose($fp);
}else {
	exit("file not found <{$filename}>\n");
}
while (true) {
	$fp = fopen($filename,"r");
	$fbin2 = fread($fp,filesize($filename));
	if ( $fbin1 != $fbin2  ) {
		echo "modified!\n";
		$fbin1 = $fbin2;
	}
	fclose($fp);
	sleep($second);
}
?>
(modify-checher ファイル名) で使えます。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
(defun modify-checher (filename)
  (let ((last-update nil))
    (labels ((checher (stream)
               (let ((present (file-write-date stream)))
                 (unless (equal last-update present)
                   (print "modified!")
                   (setf last-update present)))))
      (with-open-file (stream filename :direction :input)
        (setf last-update (file-write-date stream))
        (loop do
             (sleep 5)
             (checher stream))))))
失敬。冒頭に
(defvar filename "foo.txt")
をくわえてください。^^;
最後に、
(defun file-checher2 ()
    (file-checher filename))
もくわえてください。(file-checher2)でお題への回答になります。
最近のLinux用。 ファイル更新の定義が謎なのでいろいろ監視します。いろいろには、内容の変更、属性の変更、移動、削除が含まれます。ファイルを移動したりファイル名をリネームしても対象ファイルの監視を続けます。対象ファイルが削除されると監視をやめます。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
/* $ gcc fnotify.c -o fnotify -Wall */
#include <sys/inotify.h>
#include <unistd.h>
#include <limits.h>
#include <stddef.h>
#include <stdlib.h>
#include <stdio.h>

#define INOE_HDR_SZ offsetof(struct inotify_event, name)

static int ino_error(int ino_dev, const char *message)
{
    close(ino_dev);
    perror(message);
    return EXIT_FAILURE;
}

int observe(const char *filename)
{
    int ino_dev, ino_fd, len;
    struct inotify_event *event;
    char buffer[INOE_HDR_SZ + PATH_MAX];
    
    ino_dev = inotify_init();
    if (ino_dev == -1) {
        perror("inotify_init");
        return EXIT_FAILURE;
    }
    ino_fd = inotify_add_watch(ino_dev, filename,
                IN_ATTRIB | IN_MODIFY | IN_MOVE_SELF | IN_DELETE_SELF);
    if (ino_fd == -1) {
        return ino_error(ino_dev, filename);
    }
    event = (struct inotify_event *)buffer;
    
    while (1) {
        len = read(ino_dev, event, sizeof(buffer));
        if (len == -1 || len == 0) {
            return ino_error(ino_dev, "read");
        }
        if (event->mask & IN_ATTRIB) {
            puts("modified! (ATTRIB)");
        } else if (event->mask & IN_MODIFY) {
            puts("modified! (MODIFY)");
        } else if (event->mask & IN_MOVE_SELF) {
            puts("modified! (MOVE_SELF)");
        } else if (event->mask & IN_DELETE_SELF) {
            puts("modified! (DELETE_SELF)");
            break;
        }
    }
    close(ino_dev);
    
    return EXIT_SUCCESS;
}

int main(int argc, const char **argv)
{
    if (argc != 2) {
        fprintf(stderr, "Usage: %s filename\n", argv[0]);
        return EXIT_FAILURE;
    }
    
    return observe(argv[1]);
}
短い間隔で続けざまに変更すると、modified!が一度しか表示されないことがあるので、題意を満たしてないかも。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import os, time

filename = "a.txt"

def get_mtime():
    return os.stat(filename).st_mtime

mtime = get_mtime()
while 1:
    time.sleep(1)
    new_mtime = get_mtime()
    if mtime != new_mtime:
        mtime = new_mtime
        print "modified!"
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#! /bin/bash

filename=~/tmp/check

read -n 0 < $filename
while sleep 3; do
    if [ -N $filename ]; then
        echo 'modified!'
        sleep 1
        read -n 0 < $filename
    fi
done
posix対応。処理系はsbcl。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
(defun modify-checker (filename)
  (labels ((mtime () (sb-posix:stat-mtime (sb-posix:stat filename))))
    (let ((last-mtime (mtime)) current-mtime)
      (loop do
           (sleep 3)
           (setf current-mtime (mtime))
           (when (/= current-mtime last-mtime)
             (format t "modified!~%")
             (setf last-mtime current-mtime))))))

           
         
再帰。
1
2
3
4
5
6
7
8
(defun modify-checker (filename)
  (labels ((mtime () (sb-posix:stat-mtime (sb-posix:stat filename)))
           (check (old-t new-t)
             (when (/= old-t new-t)
               (format t "modified!~%"))
             (sleep 3)
             (check new-t (mtime))))
    (check (mtime) (mtime))))
カバレッジ稼ぎ。ロジックはPython版とほとんど同じ。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include <iostream>
#include <stdexcept>
#include <windows.h>

FILETIME mtime(const char* path)
{
    WIN32_FIND_DATA wfd;

    HANDLE h = ::FindFirstFile(path, &wfd);

    if (h == INVALID_HANDLE_VALUE)
    {
        throw std::runtime_error("file not found");
    }

    ::FindClose(h);

    return wfd.ftLastWriteTime;
}

int main()
{
    try
    {
        const char filename[] = "a.txt";

        FILETIME old_mtime = mtime(filename);

        while (true)
        {
            ::Sleep(100);

            const FILETIME new_mtime = mtime(filename);

            if (::CompareFileTime(&old_mtime, &new_mtime) != 0)
            {
                std::cout << "modified!" << std::endl;
            }

            old_mtime = new_mtime;
        }
    }
    catch (std::exception& e)
    {
        std::cerr << e.what() << std::endl;
    }
}
( ^ω^)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
function modified(f) {
	setInterval(function () {
		var s = arguments.callee;
		var r = new XMLHttpRequest;
		r.open("GET", f, true);
		r.onload = function(){
			if (s.b && s.b != r.responseText)
				alert("modified!");
			s.b = r.responseText;
		};
		r.send(null);
	}, 1000);
}

modified(filename);
JAVAと一緒。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import java.io._
def watchFile(name:String) = {
  val f = new File(name)
  var last = f.lastModified
  while(true){
    Thread.sleep(1000)
    if(f.lastModified != last) {
      println("modified!")
      last = f.lastModified
    }
  }
}
Pure Rなので多分OS非依存だと思います。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
watch.file <- function(file){
   mtime <- file.info(file)$mtime
   repeat{
       if(mtime != (file.info(file)$mtime)){
           mtime <- file.info(file)$mtime
           print("modified!")
       }
       Sys.sleep(1)
   }
}
(T,T)が、唇噛んで泣いているように見えるんです。
1
2
3
4
5
6
watch(Filename,MTime):-sleep(0.1),time_file(Filename,MTime0),modified(MTime0,MTime),watch(Filename,MTime0).
modified(_,0).
modified(T,T).
modified(_,_):-writeln(modified).

:-watch('./watch.pl',0).
Haskell練習中。
GHCのSystem.Posixモジュール使用。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import Maybe
import Directory
import System.Posix

watchFile filename = watchIt Nothing
  where watchIt Nothing = do mtime <- getModificationTime filename
                             watchIt (Just mtime)
        watchIt (Just prev) = do mtime <- getModificationTime filename
                                 if (prev < mtime) 
                                   then putStrLn "modified!"
                                   else return ()
                                 sleep 3
                                 watchIt (Just mtime)

	
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
function monitor(filename) monitor(filename, 1000, "modified!")
function monitor(filename, interval, message){
  f = getFile(filename)
  lm = f.lastModified()
  while (true){
    sleep(interval)
    m = f.lastModified()
    if (m != lm){
      println(message)
      lm = m;
    }
  }
}
Timer と TimerTask を使ってみました。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import java.io.File;
import java.util.TimerTask;

public class FileCheck extends TimerTask {

	private File targetFile;
	private long lastModify;

	public FileCheck(String filename){
		if(filename == null)
			throw new IllegalArgumentException();
		targetFile = new File(filename);
		lastModify = targetFile.lastModified();
	}

	@Override
	public void run() {
		long modify = targetFile.lastModified();
		if(modify != lastModify)
			doModifiedAction();
		lastModify = modify;
	}

	protected void doModifiedAction() {
		System.out.println("modified!");
	}
}


import java.util.Timer;

public class FileChekMain {
	public static void main(String[] args) {

		String filename = "./bin/aaa.txt";

		Timer timer = new Timer();
		timer.schedule(new FileCheck(filename), 1000L,1000L);
	}

}
$ observe-mtime -c 'echo "modified!"' filename

いつも使っているスクリプトでいけそうなのでまんま投稿します。

・複数ファイルを監視できます
・任意のコマンドを実行できます
・監視間隔を指定できます

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#!/usr/bin/env perl

use strict;
use warnings;
use Pod::Usage;
use Getopt::Long;
use File::Modified;

my $Interval = $ENV{INTERVAL} || 3; # sec
my $command;

Getopt::Long::Configure("bundling");
GetOptions(
    'command|c=s' => ¥$command,
    'help|h|?'    => sub { pod2usage() },
    ) or pod2usage;
my @files = @ARGV;
pod2usage unless $command;

### @files
### $command

my $o = File::Modified->new(files=>[ @files ]);
my @changes;

while (1) {
    if (@changes = $o->changed) {
        ### @changes
        print qx{ $command };
        $o->update;
    }
    ### sleep: $Interval
    sleep $Interval;
}

__END__

=head1 SYNOPSIS

observe-mtime -c command file [file...]

=cut
適当です。バイナリは使えないぜ。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
//HSPでファイル更新確認プログラム

*config
	//監視するファイル名
	filename = kansi.txt
	//確認する間隔(1000の1分秒 1000で1秒)
	times = 20
	//以下、プログラム
	puel = ""
	goto *gurdman
*gurdman
	notesel puel
	noteload filename
	if puel != pool {
		gosub *alert
	}
	pool = puel
	await times
*alert
	mes "modified!"
すいません、最後の行にreturnいれてください。修正版は
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
//HSPでファイル更新確認プログラム

*config
	//監視するファイル名
	filename = kansi.txt
	//確認する間隔(1000の1分秒 1000で1秒)
	times = 20
	//以下、プログラム
	puel = ""
	goto *gurdman
*gurdman
	notesel puel
	noteload filename
	if puel != pool {
		gosub *alert
	}
	pool = puel
	await times
*alert
	mes "modified!"
	return
kansi.txt は わざとですよね?
system() から test, touch, sleep, rm を呼び出しています。ほとんどシェルスクリプトです。これを敢えてawkで書くメリットはないです。
% awk -f kanshi.awk -v filename=foobar
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
BEGIN {
	srand ; rand ; rand
	tmpfile = sprintf("/tmp/kanshi%06d", rand*1000000)
	# create tmpfile
	system("touch -r " filename " " tmpfile)

	while (1) {
		r = system("test " filename " -nt " tmpfile) # if filename is newer than tmpfile
		if (r == 0) {
			# message
			print "modified!"
			# synchronize
			system("touch -r " filename " " tmpfile)
		}
		if (system("sleep 1")) break  # aborted
	}

	# remove tmpfile
	system("rm -f " tmpfile)
	exit
}
OS非依存、GHC依存(threadDelay)
チェックは1秒毎
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
module Main (main) where

import Control.Monad (when)
import Control.Concurrent (threadDelay)
import Data.Maybe (maybe, listToMaybe)
import System.Directory (getModificationTime)
import System.Environment (getArgs)
import System.IO.Error (catch)
import System.Time (ClockTime, getClockTime)

main :: IO ()
main = flip watchFile Nothing . maybe "." id . listToMaybe =<< getArgs

watchFile :: FilePath -> Maybe ClockTime -> IO ()
watchFile file prev
 = catch (threadDelay (1*1000000) >> check prev)
         (const $ watchFile file . Just =<< getClockTime)
   where 
     check Nothing      = watchFile file . Just =<< getModificationTime file
     check (Just mtime) = do { mtime' <- getModificationTime file
                             ; when (mtime < mtime') (putStrLn "modified!")
                             ; watchFile file (Just mtime')
                             }
SchemeというかGaucheですが。
しかも超亀レスですが。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
(use file.util)

(define filename "hoge.txt")

(define (make-file-modification-monitor fname sleep on-modified)
  (define (mon c)
    (let1 n (file-mtime fname)
      (unless (= c n) (on-modified))
      (sleep)
      (mon n)))
  (cut mon (file-mtime fname)))

((make-file-modification-monitor filename (cut sys-sleep 1) (cut print "modified!")))
tail -f の動作を調べて、参考にしました。
kevent, kqueue を使っているので BSD 限定だと思います。
簡単のためファイル名変更と削除には対応していません。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/event.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>


int main ( int argc, char *argv[] ){

   struct stat sb;
   struct kevent kev;
   int kq, fd;


   if( argc < 2 ){
      fprintf(stderr,"Usage: %s filename\n", argv[0] );
      return EXIT_FAILURE;
   }

   if( stat( argv[1], &sb ) == -1 ){
      fprintf(stderr,"%s is not acceessible\n", argv[1] );
      return EXIT_FAILURE;
   }

   if( !S_ISREG( sb.st_mode ) ){
      fprintf(stderr,"%s is not regular file\n", argv[1] );
      return EXIT_FAILURE;
   }

   if( (fd = open( argv[1], O_RDONLY )) == -1 ){
      fprintf(stderr,"open(\"%s\") failed\n", argv[1] );
      return EXIT_FAILURE;
   }

   kq = kqueue();

   EV_SET( &kev, fd, EVFILT_READ, EV_ADD | EV_ENABLE | EV_CLEAR , 0, 0, 0 );
   if( kevent(kq, &kev, 1, NULL, 0, NULL) < 0 ){
      fprintf(stderr,"kevent failed\n" );
      return EXIT_FAILURE;
   }
   while(1){
      kevent(kq, NULL, 0, &kev, 1, NULL);
      printf("modified!\n");
   }
   return EXIT_SUCCESS;
}
プラス評価ありがとうございます。
- 初回のイベント通知は捨てる。
- close 処理を追加。
を追加してみました。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
--- main.c.org  Thu Nov 15 23:18:57 2007
+++ main.c      Thu Nov 15 23:23:17 2007
@@ -4,7 +4,13 @@
 #include <fcntl.h>
 #include <stdio.h>
 #include <stdlib.h>
+#include <signal.h>
+#include <unistd.h>

+int received_sigint;
+void sigint_handler( int sig ){
+   received_sigint = 1;
+}

 int main ( int argc, char *argv[] ){

@@ -33,6 +39,11 @@
       return EXIT_FAILURE;
    }

+   if( signal(SIGINT, sigint_handler) == SIG_ERR ){
+      fprintf(stderr,"couldn't register SIGINT handler\n");
+      return EXIT_FAILURE;
+   }
+
    kq = kqueue();

    EV_SET( &kev, fd, EVFILT_READ, EV_ADD | EV_ENABLE | EV_CLEAR , 0, 0, 0 );
@@ -40,9 +51,16 @@
       fprintf(stderr,"kevent failed\n" );
       return EXIT_FAILURE;
    }
+   kevent(kq, NULL, 0, &kev, 1, NULL);
    while(1){
-      kevent(kq, NULL, 0, &kev, 1, NULL);
+      if( kevent(kq, NULL, 0, &kev, 1, NULL) == -1 ){
+         if( !received_sigint ){
+            perror("error");
+         }
+         break;
+      }
       printf("modified!\n");
    }
+   close(fd);
    return EXIT_SUCCESS;
 }
NSRunLoopで回すのはいいのかどうか分かりません。NSTimerを使いたくてこの形に。
一応Leopardタグ付けますがfor ~ in ~のところをNSEnumeratorを使うように書き換えればXcode2.xでもコンパイルできるはず。
あと、Leopardだとそのままずばりファイルの監視をするFSEventというAPIがあります。
TimeMachineが使ってるやつ。これはCなんですが、詳細は参考ページ参照で。
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
#import <Foundation/Foundation.h>

#define WATCH_PATH @"target path"
#define WATCH_FILE_ATTR @"target file atrributes"
#define WATCH_FILE_MODIFIED @"target file modified"

@interface Watcher : NSObject
{
    NSMutableDictionary    *targetPathes;
    NSFileManager        *manager;
    NSMutableDictionary    *fileAttributes;
}
- (id)initWithTargetPathes:(NSArray *)pathes;
- (void)checkModified;
@end

@implementation Watcher
- (id)initWithTargetPathes:(NSArray *)pathes;
{
    targetPathes    = [NSMutableDictionary dictionary];
    manager            = [NSFileManager defaultManager];
    fileAttributes    = [NSMutableDictionary dictionary];
    for (NSString *path in pathes)
    {
        [targetPathes setValue:path forKey:path];
        NSDictionary *status = [manager fileAttributesAtPath:path traverseLink:YES];
        if (status) [fileAttributes setValue:status forKey:path];
    }
    return self;
}
- (void)sendNotification:(NSString *)aPath withStatus:(id)aStatus
{
    NSString *message = [NSString stringWithFormat:@"'%@' is modified!", aPath];
    [[NSNotificationCenter defaultCenter] postNotificationName:WATCH_FILE_MODIFIED
                                                        object:message
                                                      userInfo:aStatus];
    return;
}
- (void)checkModified
{
    for (NSString *path in targetPathes)
    {
        NSAutoreleasePool *pool    = [NSAutoreleasePool new];
        NSDictionary *_prev        = [fileAttributes objectForKey:path];
        NSDictionary *_current    = [manager fileAttributesAtPath:path traverseLink:YES];
        if (_prev || _current)
        {
            if (!_current)
            {
                [fileAttributes removeObjectForKey:path];
                [self sendNotification:path withStatus:nil];
            }
            else if (![_current isEqual:_prev])
            {
                [fileAttributes setObject:[_current copy] forKey:path];
                [self sendNotification:path withStatus:_current];
            }
        }
        [pool release];
    }
    return;
}
@end

@interface FileStatusLogger : NSObject
{
    NSNotificationCenter* center;
}
- (id)initWithNotificationCenter:(NSNotificationCenter *)aCenter;
- (void)log:(NSNotification *)notice;
@end

@implementation FileStatusLogger
- (id)initWithNotificationCenter:(NSNotificationCenter *)aCenter
{
    [super init];
    [aCenter addObserver:self selector:@selector(log:) name:WATCH_FILE_MODIFIED object:nil];
    return self;
}
- (id)init
{
    return [self initWithNotificationCenter:[NSNotificationCenter defaultCenter]];
}
- (void)log:(NSNotification *)notice;
{
    NSLog(@"%@", [notice object]);
    return;
}
@end

void usage(name)
{
    printf("Usage: %s <interval> <duration> <path1> [<path2>...]\n", name);
    exit(1);
}

int main(int argc, char *argv[])
{
    if (argc < 4) usage(argv[0]);
    
    NSAutoreleasePool *pool    = [NSAutoreleasePool new];
    NSMutableArray *pathes    = [NSMutableArray array];
    NSTimeInterval interval    = strtod(argv[1], NULL);
    NSTimeInterval duration    = strtod(argv[2], NULL);
    
    if (interval <= 0) usage(argv[0]);

    int i;
    for (i = 3; i < argc; i++)
    {
        [pathes addObject:[NSString stringWithCString:argv[i]]];
    }

    Watcher *watcher            = [[Watcher alloc] initWithTargetPathes:pathes];
    FileStatusLogger *logger    = [FileStatusLogger new];
    NSTimer *timer                = [NSTimer scheduledTimerWithTimeInterval:interval
                                                                   target:watcher
                                                                 selector:@selector(checkModified)
                                                                 userInfo:nil
                                                                  repeats:YES];
    NSLog(@"monitoring start");
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
    if (duration > 0)
    {
        [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:duration]];
    }
    else
    {
        [[NSRunLoop currentRunLoop] run];
    }
    NSLog(@"monitoring end");
    
    [pool release];

    return 0;
}

116行目のscheduledTimerWithTimeInterval:target:selector:userInfo:repeats: のメソッドでcurrentRunLoopに登録されますので、 122行目の addTimer:...はいらないんじゃないかなぁ。

タイマーでぐるぐる回してるだけです

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
filename=デスクトップ&"test.txt"
TMP=filenameのファイル更新日時
監視タイマーとはタイマー
これについて
    値=1000
    時満ちた時は~
        もし(TMP<>ファイル更新日時(filename))ならば
            "modified!"を表示
            TMP=filenameのファイル更新日時
    開始

監視は1秒毎。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
fun check filename =
let
  val t = OS.FileSys.modTime filename
in
  (OS.Process.sleep o Time.fromSeconds) 1;

  if t = OS.FileSys.modTime filename then
    check filename
  else
    (print ("modified!" ^ "\n"); check filename)
end

val _ = check "hoge.txt"

まだDが出ていなかったので。 標準ライブラリが使いづらくて仕方がありません。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
import std.stdio, std.file, std.date, std.c.time;

void main(string[] args) {
    if(args.length <= 1) return;
    auto filename = args[1];
    
    d_time lastmod = mtime(filename);
    while(true) {
        d_time lastmod2 = mtime(filename);
        if(lastmod != lastmod2) {
            writeln("modified!");
            lastmod = lastmod2;
        }
        msleep(1000);
    }
}

d_time mtime(string file) {
    d_time ftc, fta, ftm;
    getTimes(file, ftc, fta, ftm);
    return ftm;
}
指定されたファイルのコピーを一時ディレクトリ(%TEMP%)に作成し、バイナリモードで元
のファイルと比較しています。タイムスタンプだけが更新された場合には検出できません。

  e.g.
    C:>monitor monitor.bat
    指定されたファイルを監視しています。
    終了するにはCtrl-Cを押してください。

    02:22:22 指定されたファイルが更新されました。

Windows XPで動作を確認。fcはWindows NTにも存在するので、NTでも動作するかもしれま
せん。

# 为了妈贡献出我自己手里的全部二。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
:: monitor.bat
@echo off
  setlocal
    set f=%TEMP%\%~nx1

    if "%1" == "" (echo %~n0 [FILE] & goto :EOF)
    if not exist %1 (echo 指定されたファイルが見つかりません。 & goto :EOF)

    echo 指定されたファイルを監視しています。
    echo 終了するにはCtrl-Cを押してください。
    echo.
    copy %1 %f% >NUL
    :loop
      fc /b %f% %1 >NUL
      if %ERRORLEVEL% equ 1 (
        for /f "tokens=2 delims=. " %%t in ('echo.^|time') do echo %%t 指定されたファイルが更新されました。
        copy %1 %f% >NUL
      )
      ping -n %1 127.0.0.1 >NUL
    goto loop
  endlocal
goto :EOF
誤りが 1つありましたので、訂正します。

20c20
<       ping -n %1 127.0.0.1 >NUL
---
>       ping -n 1 127.0.0.1 >NUL ::  1秒間スリープ
IronPythonで書いてみました。 C#とほぼ同じになりますが、C#よりタイプ量が少ないのがうれしいですね
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import clr
import System
from System.IO import *
filename = "D:/test.php"
fw = FileSystemWatcher( Path.GetDirectoryName(filename), Path.GetFileName(filename))
def handle( w,a ):
  print "modified"

fw.Changed += handle
fw.EnableRaisingEvents = True
MD5でチェックさせてみました。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
-module(checker).
-export([start/0]).

start()->
    Filename="test.html",
    loop(Filename,[]).

loop(F,C)->
    timer:sleep(1000),
    NC=erlang:md5(element(2,file:read_file(F))),
    if
        C =:= [] -> loop(F,NC);
        C =:= NC -> loop(F,C);
        C =/= NC ->
            io:format("modified!~n"),
            loop(F,NC)
    end.
こちらも処理順依存です。
ocamlが左から評価するようになったら、sleepは右のstat前にいります。
1
2
3
4
5
6
7
(*ocaml unix.cma doukaku15.ml*)
let monitor path sec =
  while true do
    if (Unix.sleep sec; Unix.stat path) <> Unix.stat path
    then print_endline "modified!"
  done
in monitor "a.txt" 2;;

処理順に依存しないようにしてみました。

1
2
3
4
5
6
7
let monitor path sec =
  let rec loop stat =
    Unix.sleep sec;
    let next_stat = Unix.stat path in
      if stat <> next_stat then print_endline "modified!";
      loop next_stat
  in loop (Unix.stat path)

	
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<?php
$filename = "testfile.txt";
$sleepTime = 1; //micro seconds

if (!($modifiedTimeOld = get_file_modified_time($filename))) {
    exit("File not found.");
}

while (true) {
    clearstatcache();
    
    if (!($modifiedTimeNew = get_file_modified_time($filename))) {
        exit("File not found.");
    }
    
    if ($modifiedTimeOld != $modifiedTimeNew) {
        echo "modified!\n";
        $modifiedTimeOld = $modifiedTimeNew;
    }
    
    usleep($sleepTime);
}

function get_file_modified_time($filename) {
    if (check_file_exist($filename)) {
        return filemtime($filename);
    } else {
        return false;
    }
}

function check_file_exist($filename) {
    if (!(file_exists($filename) && is_file($filename))) {
        return false;
    }
    return true;
}

一秒ごとにファイルの更新日時をチェックしています。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
def filename = "c:/test.txt"

def file = new File(filename)
def lastModified = file.lastModified()
while(true){
    if( lastModified != file.lastModified() ){
        println "modified! : OS非依存 : ${file.lastModified()}"
        lastModified = file.lastModified()
    }
    Thread.sleep(1000)
}

	
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Option Explicit

Dim filename, fs, objFile
Dim lastDateLastModified, nowDateLastModified

filename = WScript.Arguments(0)

Set fs = CreateObject("Scripting.FileSystemObject")
If fs.FileExists(filename) = False Then
    WScript.Echo(filename & " is not exists.")
    WScript.Quit(1)
End If

Set objFile = fs.GetFile(filename)
lastDateLastModified = objFile.DateLastModified

Do
    nowDateLastModified = objFile.DateLastModified
    If lastDateLastModified <> nowDateLastModified Then
        WScript.Echo "modified!"
        lastDateLastModified = nowDateLastModified
    End If
    WScript.Sleep(10000)
Loop

Index

Feed

Other

Link

Pathtraq

loading...