perl File::Find confusion

I’m trying to write a script that will find all files (named *.conf) in a particular directory that have not been modified in n days. Here’s what I have so far:

use File::Find;
$home = $ENV{“HOME”};
$search_dir = $home;
find(&wanted, search_dir); sub wanted { if (/.conf/ && int(-M _) > 7) {
print "$File::Find::name
";
}
}

But I’m having 2 problems:

  1. I touched one of the files and it still comes up in the search
  2. If I muck with the directory path (e.g. $search_dir = $home . “/subdir”) it doesn’t find anything

Can someone help?

The problem with using the magic _ filehandle is that it will contain the filesystem information for whatever file was last stat()ed. Since you’re not doing any stats or other file tests on your files before using -M, you’ll get the results from some whatever other file happens to be in there.

If you change it to something like this, you should get the results you want:



use strict;
use warnings;

use File::Find;

my $home = $ENV{HOME};
my $search_dir = $home;
find(\&wanted, $search_dir);

sub wanted {
    if (/.conf$/ && int(-M $File::Find::name ) > 7) {
        print "$File::Find::name 
";
    }
}


Note that I’ve also turned on the strict and warnings pragmas, which will give you helpful information when debugging, and prevent you from doing naughty stuff like using undeclared variables.

You left out a “$”, and for correctness, you want to escape the “.” in your regexp. You want this:



  if (/.conf$/ && int(-M _) > 7) {


To be this:



  if (/\.conf$/ && int(-M $_) > 7) {


On preview: Friedo’s suggestion to use $File::Find::name instead is a good one.

You can even just omit the "File::Find::name" and it will use _ by default, which is the current filename being considered. You could leave out the “int()” as well, since you are comparing with a >.

BTW, http://perlmonks.org/ is the best place to ask perl questions.

Note that $_ is just the filename without the directory, so this will only work if you’re searching in the current working directory (or you are keeping track and cwd as necessary.) That’s why it’s better to use $File::Find::name which contains the complete pathname.

The interface to File::Find is full of weird quirks like this and leaves a lot to be desired.

I believe it’s working now. Thanks! The magic “_” was the problem.

Which File::Find does for you.

Why is it better? We know we’re using File::Find, so $_ has the filename we want to look at. That’s the way File::Find is designed, to make it easy to use the defaults. I’d make a claim that it is better to use the defaults, easier and cleaner.

Look at the first clause, /.conf$/? Which file is it looking at?

Would you suggest changing that simple expression to the more complicated
File::Find::name =~ /\.conf/ ‘just in case’?

No, I’d suggest not using File::Find at all, because I can obviously never remember what it puts in $_ and how it treats the cwd :smiley:

I like File::Find::Rule better.

I’ll second that… :slight_smile:

I actually wrote my own: File::OFind. Mine is an object oriented version. I never liked File::Find for several reasons. One is the use of global variables. The other is that the “wanted” function either ends up being your entire program, or that you save all of your results in a “global” array in wanted and then use it in your main program.

Mine allows you a more object oriented way of fetching files, and I think it’s a lot easier to understand:




use File::OFind;
use strict;
use warnings;

my $directory = "foo";
my $find = File::OFind->new($directory);

while (my $file = $find->next()) {
    next unless ($find->Suffix() eq "conf");
    print "The file is $file
";
    print "The directory is " . $file->Dirname() . "
";
    print "The basename is " . $file->Basename() . "
";
}



If you’re interested in it, you can get it from http://dl.dropbox.com/u/433257/OFind.pm. You can generate the documentation from “perldoc File::OFind” once you put it in the @INC directory. I make no guarantees, and you’ll still owe me a beer (see license).

By the way, don’t forget find2perl which will write the function for you:



$ #find foo -name "*.conf" <-- The "find" command you want
$ find2perl foo -name "*.conf"   #Replace "find" with "find2perl"

#! /usr/bin/perl -w
    eval 'exec /usr/bin/perl -S $0 ${1+"$@"}'
        if 0; #$running_under_some_shell

use strict;
use File::Find ();

# Set the variable $File::Find::dont_use_nlink if you're using AFS,
# since AFS cheats.

# for the convenience of &wanted calls, including -eval statements:
use vars qw/*name *dir *prune/;
*name   = *File::Find::name;
*dir    = *File::Find::dir;
*prune  = *File::Find::prune;

sub wanted;

# Traverse desired filesystems
File::Find::find({wanted => \&wanted}, 'foo');
exit;
sub wanted {
    /^.*\.conf\z/s
    && print("$name
");
}


qazwart, you should put that on CPAN. I’ll help you create a distribution for it if you haven’t done that before.

I was thinking about putting on CPAN, but I wasn’t sure if the code is up to snuff for a major distribution. Plus, I’d have to write a set of tests and put together a distribution.

I’d be very grateful if you could help me with the distribution. Thx.