Monitor a set of files for changes and execute a command on them when they do

痞子三分冷 提交于 2019-11-28 01:31:54

问题


The (command line) interface I have in mind is like so:

watching FILE+ do COMMAND [ARGS] (and COMMAND [ARGS])*

Where any occurrence of "{}" in COMMAND is replaced with the name of the file that changed. And note that "do" and "and" are keywords.

For example:

> watching foo.txt bar.txt do scp {} somewhere.com:. and echo moved {} to somewhere

Or:

> watching foo.c do gcc foo.c and ./a.out

I'm not wedded to that interface though. I'll add my script that does that as an answer and see if anyone has anything better or ways to improve it.


回答1:


#!/usr/bin/perl
# Run some commands whenever any of a set of files changes (see USAGE below).
# Example:
# ./watching.pl foo.txt bar.txt do scp foo.txt remote.com:. and cat bar.txt
# To only do something to the file that changed, refer to it as {}.

$| = 1;  # autoflush

my $p = position("do", @ARGV); # position of 1st occurrence of "do" in @ARGV.
if (@ARGV < 3 || $p == -1 || !($p >= 1 && $p < $#ARGV)) {
  die "USAGE: watching FILE+ do COMMAND [ARGS] (and COMMAND [ARGS])*\n";
}

my $cmdstr = join(' ', splice(@ARGV, $p+1));  # grab stuff after the "do"
my @cmds = split(/\s+and\s+/, $cmdstr);
pop(@ARGV);  # remove the "do" on the end.
my @targets = @ARGV;
print "Watching {", join(' ', @targets), "} do (", join('; ', @cmds), "):\n";

# initialize the %last hash for last mod time of each file.
for my $t (@targets) {
  ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,
   $atime,$mtime,$ctime,$blksize,$blocks) = stat($t);
  $last{$t} = $mtime;
}

my $i = 1;
while(1) {
  if($i % (45*60) == 0) { print "."; }

  for my $t (@targets) {
    ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,
     $atime,$mtime,$ctime,$blksize,$blocks) = stat($t);

    if ($mtime != $last{$t}) {
      print "\nCHANGE DETECTED TO $t\n";
      for (@cmds) { my $tmp = $_; $tmp =~ s/\{\}/$t/g; system($tmp); }
      $last{$t} = $mtime;
    }
  }
  sleep(1);
  $i++;
}


# Call like so: position($element, @list).
sub position {
  my $x = shift;
  if(@_==0) { return -1; }
  if($x eq $_[0]) { return 0; }
  shift;
  my $p = position($x,@_);
  if($p==-1) { return -1; }
  return 1+$p;
}



回答2:


You can use entr, e.g.

ls foo.txt bar.txt | entr sh -c 'rsync -vuar *.txt somewhere.com:. && echo Updated'

Unfortunately you can't check which file has been changed, but using rsync -u will automatically skip files which haven't been changed.

Here is the second example:

ls foo.c | entr sh -c 'gcc foo.c && ./a.out'

or simply use make, e.g.

ls -d * | entr sh -c 'make && make test'

To watch the directory for changes, use -d parameter wrapped inside a shell loop, e.g.:

while true; do find path/ | entr -d do_stuff; done



回答3:


Please check the inotify tools.




回答4:


I just found this every_change script written in perl, that's very similar to the one I posted in my answer.

This script kicks ass for code development. Watches a file and runs it (or something else) every time it changes. Write your code in one window, and watch it automatically execute in another.

So basically it does something every time a file changes.



来源:https://stackoverflow.com/questions/393176/monitor-a-set-of-files-for-changes-and-execute-a-command-on-them-when-they-do

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!