user
user

Reputation: 6947

Find out whether a file newer than a given date/time exists in a directory?

Let's say I have a list of directories, each of which may or may not contain subdirectories which I also want to consider.

Let's also say I have a list of timestamps, one for each directory in the list (but not subdirectories). These are known as date and time with an implicit time zone, so can fairly easily be converted to a Unix timestamp if that makes it easier to compare.

How do I, for each of those directories listed, find out whether there exists any file within the directory which is newer (in terms of mtime or ctime, but not atime) than the timestamp that I have for the directory in question?

I'm not really interested in which specific file is newer than the timestamp, only whether or not any such files exist at all.

Basically, I want to write a script that when run performs a specific action if any file inside any one of a set of directories has been changed after a given point in time, and need to come up with a way to detect whether anything has been changed.

Upvotes: 1

Views: 3693

Answers (2)

Adam Katz
Adam Katz

Reputation: 16138

Here's a way to do it without any modules or external calls:

for my $file (glob "* ./**/* .[!.]*") {  # include subdirs & dot files
  my $file_time = $^T - 86400 * (-M $file);
  print "$file is newer than $timestamp\n" if $file_time >= $timestamp;
}

-M $file will give you "script start time minus file modification time, in days" with sufficient precision to be multiplied by 86400 to get "age in seconds". To get the file's modification time, perform that multiplication and then subtract the resulting product from $^T (aka $BASETIME, though I can't get that variable working), the script's start time.

This example simply loops through every item in the current directory and prints that it is newer than the given timestamp when that is true.

Upvotes: 1

amon
amon

Reputation: 57600

Your problem can be translated into multiple easy subproblems

  1. Q: How do I recursively look at each file inside a directory?

    A: use File::Find. This would look a bit like

    use File::Find;
    
    find sub {
      return unless -f;
      if (file_is_newer_than($timestamp)) {
        do something;
      },
    }, $top_dir;
    
  2. Q: How do I do this for multiple directories?

    A: Wrap it in a foreach loop, e.g.

    for my $dir_time (["./foo", 1234567890], ["./bar", 1230987654]) {
      my ($top_dir, $timestamp) = @$dir_time;
      # above code
    }
    
  3. Q: How do I determine if the file is newer?

    A: stat it for mtime or ctime, then compare result with your timestamp. E.g.

    use File::stat;
    
    say "$_ is new!" if stat($_)->mtime > $timestamp;
    
  4. Q: I'm only interested whether or not any such files exist at all. How can I short curcuit the find?

    A: Tricky one. We can't just return from the find, because that would just exit from the coderef we passed it. Instead, we can use the exceptions-for-control-flow antipattern:

    eval {
      find {
        wanted => sub {
          return unless -f;
          die "New file found\n" if stat($_)->mtime > $timestamp;
        },
        no_chdir => 1,
      } $top_dir;
    };
    if ($@) {
      # I should really use exception objects here…
      if ($@ eq "New file found\n") {
        say "New file in $top_dir found";
      } else {
        die $@;  # rethrow error
      }
    }
    

    I set the no_chdir option so that I don't have to restore the correct working directory in the exception handler.

    Or we could use loop control on labeled blocks:

    DIR: for my $dir_time (...) {
      my ($top_dir, $timestamp) = @$dir_time;
      RECURSION: {   
        find {
          wanted => sub {
            return unless -f;
            last RECURSION if stat($_)->mtime > $timestamp; # exit the RECURSION block
          },
          no_chdir => 1,
        } $top_dir;
        # if we are here, no newer file was found.
        next DIR; # make sure to skip over below code; go to next iteration
      }
      # this code only reached when a newer file was found
      say "New file found";
    }
    

    While this doesn't abuse exceptions for control flow, this will trigger warnings:

    Exiting subroutine via last
    

    We can silence this with no warnings 'exiting'.

NB: All code in here is quite untested.

Upvotes: 7

Related Questions