system-admin intermediate

How to monitor file changes in a directory using Perl?

Question

How to monitor file changes in a directory using Perl?

Monitoring file changes in a directory in Perl is typically done by periodically polling the directory contents and comparing file modification times (mtime), given that Perl's core does not provide event-driven file system notifications. This technique works across platforms without external modules, making it ideal for sandboxed or restricted environments.

Perl Concepts Used

  • opendir, readdir, and closedir for directory traversal.
  • stat to get file metadata, with the modification time at index 9.
  • Hashes (%hash) to store snapshots of file states for comparison.
  • Context sensitivity: stat used in list context to access mtime.
  • Core module Time::HiRes to sleep for fractional seconds (available since Perl 5.8).
  • Use of $|=1 to autoflush STDOUT for immediate output display.

Important Fixes for Sandboxed Execution

  • Use a fixed, small number of polling iterations (3) to prevent infinite loops and long run times.
  • Reduce the polling interval so total runtime stays under 1 second.
  • Print informative output immediately via autoflush to track changes clearly.

Runnable Perl Example: Poll Directory Changes 3 Times with 0.2s Interval

use strict;
use warnings;
use Time::HiRes qw(sleep);

$| = 1;  # Autoflush STDOUT

my $dir = '.';
my %files_snapshot;

sub scan_directory {
    my ($directory) = @_;
    my %current;

    opendir(my $dh, $directory) or die "Cannot open directory '$directory': $!";
    while (my $file = readdir($dh)) {
        next if $file eq '.' or $file eq '..';
        my $path = "$directory/$file";
        next unless -f $path;       # Only regular files
        my $mtime = (stat($path))[9];
        $current{$file} = $mtime;
    }
    closedir($dh);
    return %current;
}

# Initial snapshot
%files_snapshot = scan_directory($dir);
print "Starting directory monitoring on '$dir'\n";

for my $iteration (1 .. 3) {
    sleep(0.2);
    my %current = scan_directory($dir);

    for my $file (keys %current) {
        if (!exists $files_snapshot{$file}) {
            print "Added file: $file\n";
        }
        elsif ($current{$file} != $files_snapshot{$file}) {
            print "Modified file: $file\n";
        }
    }

    for my $file (keys %files_snapshot) {
        if (!exists $current{$file}) {
            print "Deleted file: $file\n";
        }
    }

    %files_snapshot = %current;
}
print "Monitoring completed.\n";

Explanation

This script:

  • Takes an initial snapshot of all regular files in the current directory, storing their modification times.
  • Runs 3 polling cycles, each pausing 0.2 seconds (keeping total run time under ~1 second).
  • After each poll, compares the current file list to the snapshot, printing any added, modified, or deleted files.
  • Updates the snapshot for the next iteration.

This avoids infinite loops and heavy filesystem load, making it safe for restricted sandboxes while demonstrating how to monitor changes with core Perl functions.

Common Pitfalls

  • Infinite loops: Avoid while(1) loops in time-constrained environments.
  • Timestamp granularity: Some file systems only update mtime to the nearest second, so rapid changes may be missed.
  • Symbolic links and directories: This example only considers regular files (-f), so changes in directories or symlinks are not tracked.
  • Output buffering: Without $|=1, output may be delayed or buffered, obscuring real-time changes.
  • Polling interval: Smaller intervals detect changes faster but consume more CPU.

This simple polling method is straightforward, portable, and suitable for sandboxed Perl environments where event-based watching modules like Linux::Inotify2 or File::ChangeNotify are unavailable.

Verified Code

Executed in a sandbox to capture real output. • v5.34.1 • 629ms

Tip: edit code and use “Run (Browser)”. Server runs always execute the published, verified snippet.
STDOUT
Starting directory monitoring on '.'
Monitoring completed.
STDERR
(empty)

Was this helpful?

Related Questions