#!/usr/bin/perl # run_when_changed runs a command when a file (or files) is changed # and is released under the terms of the GNU GPL version 3, or any # later version, at your option. See the file README and COPYING for # more information. # Copyright 2017 by Don Armstrong . use warnings; use strict; use Pod::Usage; use Linux::Inotify2; use File::Basename; =head1 NAME run_when_changed - runs a command when a file (or files) is changed =head1 SYNOPSIS run_when_changed [file1] [command ...] run_when_changed [file1] [filen] -- [command ...] If only a single file is to be watched, the first argument is the file, and all remaining arguments are the command. If multiple files are to be watched, all arguments until the first -- argument are files to be watched. If you wish to use a command containing -- and watch a single file, -- must be the second argument. =head1 EXAMPLES run_when_changed file.Rnw make file.pdf; run_when_changed file.Rnw file_2.Rnw -- make file.pdf; =cut my @USAGE_ERRORS; if (@ARGV < 2) { push @USAGE_ERRORS,"You must provide at least a file and a command to run"; } pod2usage(join("\n",@USAGE_ERRORS)) if @USAGE_ERRORS; my ($watch_files,$command) = identify_files_and_command(@ARGV); my $watching = set_up_file_watching($watch_files); watch_and_run_command($watching,$command); sub identify_files_and_command { my (@arguments) = @_; my $file_end=0; my $command_start=1; for my $i (0..$#arguments) { if ($arguments[$i] eq '--') { $file_end=$i-1; $command_start=$i+1; last; } } my @files = @arguments[0..$file_end]; my @command = @arguments[$command_start..$#arguments]; return (\@files,\@command); } sub set_up_file_watching { my ($files) = @_; my $watching = {files => $files}; my $inotify = new Linux::Inotify2 or die "Unable to create new inotify object: $!"; for my $file (@{$files}) { my $watched_dir = dirname($file); next if exists $watching->{dirs_watched}{$watched_dir}; $inotify->watch($watched_dir, IN_CLOSE_WRITE|IN_MOVED_TO|IN_CREATE, ) or die "Unable to watch file $file: $!"; $watching->{dirs_watched}{$watched_dir} = 1; } $watching->{inotify} = $inotify; return $watching; } sub watch_and_run_command { my ($watching,$command) = @_; while () { my @events = $watching->{inotify}->read; for my $event (@events) { my $run_command = 0; for my $file (@{$watching->{files}}) { if ($event->w->name eq dirname($file) and $event->name eq basename($file) ) { $run_command = 1; } } if ($run_command) { system(@{$command}); } } } } __END__