Recursively Find All Files Newer Than a Given Time

Recursively find all files newer than a given time

This is a bit circuitous because touch doesn't take a raw time_t value, but it should do the job pretty safely in a script. (The -r option to date is present in MacOS X; I've not double-checked GNU.) The 'time' variable could be avoided by writing the command substitution directly in the touch command line.

time=$(date -r 1312603983 '+%Y%m%d%H%M.%S')
marker=/tmp/marker.$$
trap "rm -f $marker; exit 1" 0 1 2 3 13 15
touch -t $time $marker
find . -type f -newer $marker
rm -f $marker
trap 0

find files modified within given time range

-newermt primary doesn't accept a time range in any format. To select files modified within let's say the period A-B, you need two -newermts; one to include files newer than A, the other to exclude files newer than B.

Further, there are two edge cases that need to be dealt with:

  1. The user might enter 08 or 09 as both are valid hours. But as both have a leading zero, Bash would treat them as octal numbers in an arithmetic context, and raise an error since 8 and 9 are not valid digits in base 8.
  2. When the user entered 0, to include files modified at 00:00 too, inclusive -newermt's argument has to be yesterday's 23:59:59.

So, I would do it like this instead:

#!/bin/bash -
LC_COLLATE=C
read -rp 'hour ([0]0-23): ' hour
case $hour in
(0|00)
find /home/mikepnrs \
-newermt 'yesterday 23:59:59' \
! -newermt '00:59:59' ;;
(0[1-9]|1[0-9]|2[0-3])
find /home/mikepnrs \
-newermt "$((10#$hour-1)):59:59" \
! -newermt "$hour:59:59" ;;
(*)
printf 'invalid hour: %q\n' "$hour" >&2
exit 1
esac

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

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.

How can I recursively find all files in current and subfolders based on wildcard matching?

Use find:

find . -name "foo*"

find needs a starting point, so the . (dot) points to the current directory.

How to recursively find the latest modified file in a directory?


find . -type f -printf '%T@ %p\n' \
| sort -n | tail -1 | cut -f2- -d" "

For a huge tree, it might be hard for sort to keep everything in memory.

%T@ gives you the modification time like a unix timestamp, sort -n sorts numerically, tail -1 takes the last line (highest timestamp), cut -f2 -d" " cuts away the first field (the timestamp) from the output.

Edit: Just as -printf is probably GNU-only, ajreals usage of stat -c is too. Although it is possible to do the same on BSD, the options for formatting is different (-f "%m %N" it would seem)

And I missed the part of plural; if you want more then the latest file, just bump up the tail argument.

Shell Script — Get all files modified after date

as simple as:

find . -mtime -1 | xargs tar --no-recursion -czf myfile.tgz

where find . -mtime -1 will select all the files in (recursively) current directory modified day before. you can use fractions, for example:

find . -mtime -1.5 | xargs tar --no-recursion -czf myfile.tgz


Related Topics



Leave a reply



Submit