#!/usr/bin/perl -w # # $Id: fbd,v 1.4 2003/08/01 02:10:56 jmates Exp $ # # The author disclaims all copyrights and releases this script into the # public domain. # # Finds files with same (or similar) modification times as specified # file or user-supplied date. require 5; use strict; my $VERSION; ($VERSION = '$Revision: 1.4 $ ') =~ s/[^0-9.]//g; # what stat() colume to read time from (e.g. mtime, atime, ctime) my $timef = 9; use Getopt::Std; my %opts; getopts('h?f:d:a:b:p:s:', \%opts); help() if exists $opts{'h'} or exists $opts{'?'}; help() unless @ARGV; my $src_date; if (exists $opts{d}) { $src_date = $opts{d} or die "error: could not parse empty date\n"; # treat all numeric as epoch date, otherwise attempt to parse unless ($src_date =~ /^\d+/) { require Date::Parse; $src_date = Date::Parse::str2time($src_date); die "error: could not parse supplied date\n" unless $src_date; } } if (exists $opts{f}) { $src_date = (stat $opts{f})[$timef]; die "error: could not read date from $opts{f}\n" if not $src_date or $src_date !~ /^\d+$/; } my $skip = $opts{'s'} if exists $opts{'s'}; my $prune = $opts{'p'} if exists $opts{'p'}; my ($fudge, $min_date, $max_date); if (exists $opts{a}) { $fudge = duration2seconds($opts{a}); $min_date = $src_date - $fudge; $max_date = $src_date + $fudge; } if (exists $opts{b}) { $fudge = duration2seconds($opts{b}); $min_date = $src_date - $fudge; $max_date = $src_date unless $max_date; } use File::Find; for my $parent (@ARGV) { find { no_chdir => 1, wanted => sub { my $file_date = (stat $_)[$timef]; if ($prune and -d _) { my $result = eval "return 1 if (" . $prune . ");"; if ($@) { chomp $@; die "error: prune eval failure: ", $@; # croak on errors } if ($result) { $File::Find::prune = 1; return; } } if ( ( defined $fudge and ($file_date >= $min_date and $file_date <= $max_date) ) or ($file_date eq $src_date) ) { if ($skip) { my $result = eval "return 1 if (" . $skip . ");"; if ($@) { chomp $@; die "error: skip eval failure: ", $@; # croak on errors } if ($result) { return; } } print $File::Find::name, "\n"; } } }, $parent } ###################################################################### # # SUBROUTINES # takes duration such as "2m3s" and returns number of seconds. sub duration2seconds { my $tmpdur = shift; my $seconds; # how to convert short human durations into seconds my %factor = ( w => 604800, d => 86400, h => 3600, m => 60, s => 1, ); # assume raw seconds for plain number if ($tmpdur =~ m/^\d+$/) { $seconds = $tmpdur * 60; } elsif ($tmpdur =~ m/^[wdhms\d\s]+$/) { # match "2m 5s" style input and convert to seconds while ($tmpdur =~ m/(\d+)\s*([wdhms])/g) { $seconds += $1 * $factor{$2}; } } else { die "Error: unknown characters in duration.\n"; } unless (defined $seconds and $seconds =~ m/^\d+$/) { die "Error: unable to parse duration.\n"; } return $seconds; } # a generic help blarb sub help { print <<"HELP"; Usage: $0 [opts] searchdir1 [sd2 .. sdN] Finds files with similar date to specified date or file. Options for version $VERSION: -h/-? Display this message. -f ff Read modify time to compare with from specified file. -d dd Specify modify time manually. -a xx Allow xx seconds or shorthand duration fuzz around lookup time. -b yy Allow xx seconds before lookup (makes -a "after time") -s xx Perl expression to skip files. -p xx Perl expression to prune directories from search. Run perldoc(1) on this script for additional documentation. HELP exit; } ###################################################################### # # DOCUMENTATION =head1 NAME fbd - find files by date =head1 SYNOPSIS Find files in /etc with the same modification date as /etc/passwd. $ fbd -f /etc/passwd /etc List files under /tmp and /var/tmp modified within five minutes of the current date. $ fbd -d "`date`" -a 5m /tmp /var/tmp =head1 DESCRIPTION =head2 Overview Provides means to list files under specified search directories that have or have similar modification (mtime) dates set. =head2 Normal Usage $ fbd [opts] searchdir1 [sd2 .. sdN] See L<"OPTIONS"> for details on the command line switches supported. Either a single or multiple search directories must be specified. Each directory will be searched recursively. A date to search by must be supplied either from a file with B<-f> or manually with B<-d>. =head1 OPTIONS This script currently supports the following command line switches: =over 4 =item B<-h>, B<-?> Prints a brief usage note about the script. =item B<-f> I Read modification time from the specified file. =item B<-d> I Uses specified time in epoch or Date::Parse-compatible format for the value to compare other files with. =item B<-a> I Without B<-b>, allows files modified within I to match. The duraction can either be in raw seconds or a short-hand "2m5s" format. This means B<-a> I<3h> without B<-b> will match files modified within three hours either side of the target date. Mnemonic: "around." With B<-b>, allows files modified I after the target date to match. Mnemonic: "after." The short-hand duration notation supports w for weeks, d for days, h for hours, m for minutes, and s for seconds. Multiple groups add together, such that 1m1s1s adds up to 62 seconds. =item B<-b> I Allows files modified I before the target date to match. Assuming no B<-a> is specified, B<-b> I<120> would match files modified at the target date, or up to 120 seconds before that time. =item B<-s> I Perl expression that will result in the current item (stored in $_) being skipped from being listed if the expression turns out to be true and the file in question would otherwise match. Example: -s '-d _' Would exclude directories from being matched, via the cached stat information using the special _ notation. =item B<-p> I Perl expression that will result in the current directory (stored in $_) and anything below that directory being "pruned" from the search. For example, one can easily prune out all directories lower than the one supplied as an argument by using the special $parent variable to check against the current directory; essentially, this turns off the default recursive behaviour of File::Find. -p '$parent ne $_' =back =head1 SECURITY Disable or remove the -s and -p options if the script is used to perform actions via sudo(8) or other user-changing methods, as the options in question execute arbitrary perl code. =head1 BUGS =head2 Reporting Bugs Newer versions of this script may be available from: http://sial.org/code/perl/ If the bug is in the latest version, send a report to the author. Patches that fix problems or add new features are welcome. =head2 Known Issues No known bugs. =head1 SEE ALSO perl(1), Date::Parse =head1 AUTHOR Jeremy Mates, http://sial.org/contact/ =head1 COPYRIGHT The author disclaims all copyrights and releases this script into the public domain. =head1 VERSION $Id: fbd,v 1.4 2003/08/01 02:10:56 jmates Exp $ =head1 SCRIPT CATEGORIES Utilities Unix/System_administration =cut