11
opendir(DIR,"$pwd") or die "Cannot open $pwd\n";
    my @files = readdir(DIR);
    closedir(DIR);
    foreach my $file (@files) {
        next if ($file !~ /\.txt$/i);
        my $mtime = (stat($file))[9];
        print $mtime;
        print "\n";
    }

Basically I want to note the timestamp of all the txt files in a directory. If there is a subdirectory I want to include files in that subdirectory too.

Can someone help me in modifying the above code so that it includes subdirectories too.

if i am using the code below in windows iam getting timestamps of all files which are in folders even outside my folder

 my @dirs = ("C:\\Users\\peter\\Desktop\\folder");
    my %seen;
    while (my $pwd = shift @dirs) {
            opendir(DIR,"$pwd") or die "Cannot open $pwd\n";
            my @files = readdir(DIR);
            closedir(DIR);
            #print @files;
            foreach my $file (@files) {
                    if (-d $file and !$seen{$file}) {
                            $seen{$file} = 1;
                            push @dirs, "$pwd/$file";
                    }
                    next if ($file !~ /\.txt$/i);
                    my $mtime = (stat("$pwd\$file"))[9];
                    print "$pwd $file $mtime";
                    print "\n";
            }
    }
Peter
  • 2,719
  • 4
  • 25
  • 55

5 Answers5

19

File::Find is best for this. It is a core module so doesn't need installing. This code does the equivalent of what you seem to have in mind

use strict;
use warnings;

use File::Find;

find(sub {
  if (-f and /\.txt$/) {
    my $mtime = (stat _)[9];
    print "$mtime\n";
  }
}, '.');

where '.' is the root of the directory tree to be scanned; you could use $pwd here if you wish. Within the subroutine, Perl has done a chdir to the directory where it found the file, $_ is set to the filename, and $File::Find::name is set to the full-qualified filename including the path.

Borodin
  • 126,100
  • 9
  • 70
  • 144
  • 1
    [Not everybody will agree with you](https://www.socialtext.net/perl5/alternatives_to_file_find) on File::Find being best. – salva Mar 07 '12 at 12:12
  • 1
    File::Find is slow, and the API is frustrating, but I think it is ideal for something trivial like this. I would like to hear what anyone has against it. – Borodin Mar 08 '12 at 15:22
  • 1
    @salva Very old comment I know, but the link now points to a login page. – Matthew Lundberg Oct 19 '16 at 16:08
8
use warnings;
use strict;

my @dirs = (".");
my %seen;
while (my $pwd = shift @dirs) {
        opendir(DIR,"$pwd") or die "Cannot open $pwd\n";
        my @files = readdir(DIR);
        closedir(DIR);
        foreach my $file (@files) {
                next if $file =~ /^\.\.?$/;
                my $path = "$pwd/$file";
                if (-d $path) {
                        next if $seen{$path};
                        $seen{$path} = 1;
                        push @dirs, $path;
                }
                next if ($path !~ /\.txt$/i);
                my $mtime = (stat($path))[9];
                print "$path $mtime\n";
        }
}
Gray
  • 115,027
  • 24
  • 293
  • 354
perreal
  • 94,503
  • 21
  • 155
  • 181
  • 1
    `$file !~ /^\.*$/` is `$file =~ /[^.]/`. But I have been severely reprimanded in the past for excluding names that are just three dots or longer, as they are valid names for Linux files. So the test *should* be `$file !~ /^\.\.?$/` – Borodin Mar 07 '12 at 12:05
  • @perreal if i want to open files and search soome particular string in the file and seperate such files, I am thinking to do that with OPEN INPUT, $file , then $lines = then seraching on it , is it fine ? or there is some other easy way – Peter Mar 07 '12 at 13:40
  • You can use `grep` for that. If you don't want to use any tools then it is fine to do what you suggest. – perreal Mar 07 '12 at 14:15
  • Hi can you all see the edited question and help me out .. i tried the code in windows , but i am getting timestamps of files which are in folders outside my specified folder too, i have pasted the code in question – Peter Mar 07 '12 at 16:06
  • 1
    Can you try these changes? `push @dirs, "$pwd\\$file"; my $mtime = (stat("$pwd\\$file"))[9];` – perreal Mar 07 '12 at 17:23
  • I'd add a bit to check for symbolic links too. I've made lots of dumb mistakes going through parts of the directory structure many times more than I needed because they linked to other parts of the filesystem. – brian d foy Mar 08 '12 at 02:16
  • The `-d` test and `! $seen{$file}` both need to work with `"$pwd/$file"` or this will not do what you intended. Also, the second half of the `for` loop needs to be enclosed in an `else` block (or, better, an `elsif ( -f "$pwd/$file" )` to reject symlinks) as it doesn't apply to directories – Borodin May 24 '16 at 00:57
  • Can't believe I'm missing it, but didn't OP want to visit subdirectories? The solution given here doesn't descend into subdirectories for me. (perl 5.22.1) – Chap Aug 16 '17 at 19:34
  • Yeah this doesn't work and suffers from a number of bugs. $file is not relative to the dir so -d $file won't do what you want and directories within directories won't work. – Gray May 07 '18 at 18:24
  • I think the hash "seen" is not needed at all. namely "... if $seen{$path};" will be always false – Alejadro Xalabarder Apr 08 '19 at 16:57
4

use File::Find::Rule

File::Find::Rule is a friendlier interface to File::Find. It allows you to build rules which specify the desired files and directories.

Boris Däppen
  • 1,186
  • 7
  • 20
salva
  • 9,943
  • 4
  • 29
  • 57
1

You can use recursion: define a function that goes through files and calls itself on directories. Then call the function on the top directory.

See also File::Find.

choroba
  • 231,213
  • 25
  • 204
  • 289
0

if i am using the code below in windows iam getting timestamps of all files which are in folders even outside my folder

I suspect that the problem may have been an issue with the . and .. directories which if you tried to follow them would have taken you up the directory tree. What you were missing was a:

foreach my $file (@files) {
    # skip . and .. which cause recursion and moving up the tree
    next if $file =~ /^\.\.?$/;
    ...

Your script also suffers from a couple of bugs. $file is not relative to the $dir so -d $file would only work in the current directory and not below.

Here's my fixed version:

use warnings;
use strict;

# if unix, change to "/";
my $FILE_PATH_SLASH = "\\";    
my @dirs = (".");
my %seen;
while (my $dir = shift @dirs) {
        opendir(DIR, $dir) or die "Cannot open $dir\n";
        my @files = readdir(DIR);
        closedir(DIR);
        foreach my $file (@files) {
                # skip . and ..
                next if $file =~ /^\.\.?$/;
                my $path = "$dir$FILE_PATH_SLASH$file";
                if (-d $path) {
                        next if $seen{$path};
                        $seen{$path} = 1;
                        push @dirs, $path;
                }
                next unless $path ~= /\.txt$/i;
                my $mtime = (stat($path))[9];
                print "$path $mtime\n";
        }
}
Gray
  • 115,027
  • 24
  • 293
  • 354