challenge ファイル更新の監視

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

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

Posted feedbacks - Flatten

Nested 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)

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
}

$ 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のパワーの一つだと思うのでいいんじゃないでしょうか。

ファイルの存在チェックとタイムスタンプのチェックのみを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

POE持ち出してきた自分はもっと反則なので大丈夫だと思います:)

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)
    }
  }
}

同じ方針ですが、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))))

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]);
}

おお、キレイ!
勉強になります。ありがとうございます。

あと自分のでは、8行目と11行目の間に更新されると"modified!"の出力が一回少なくなってしまいますね。

短い間隔で続けざまに変更すると、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)