Reputation: 59
My Perl script is supposed to open all the files in a directory and search for a pattern inside them and print the whole line containing the pattern. Below is the code. The code is unable to open the file.
my $dir = 'dir1/dir2';
opendir (DIR, $dir) or die $!;
my @dir = readdir DIR;
foreach my $item (@dir) {
open(FILE, "<", "file.txt")
or die "Can't open < file.txt: $!";
while($line= <FILE>) {
print "$line" if $line=~ /pattern/;
}
close FILE;
}
closedir DIR;
Please suggest some way to make it work.
Upvotes: 1
Views: 801
Reputation: 66883
The readdir returns just names (file.txt
), not paths (not dir1/dir2/path.txt
).
So when the program tries to open $item
† it turns out that there is no such a thing -- need to open "$dir/$item"
. Or, prepend that to every entry in the filelist and then work away with correct paths
use warnings;
use strict;
my $dir = 'dir1/dir2';
opendir (my $dh, $dir) or die $!;
my @entries = map { "$dir/$_" } readdir $dh;
closedir $dh;
foreach my $item (@entries) {
next if not -f $item;
open(my $fh, "<", $item) or die "Can't open < $item: $!";
while(my $line = <$fh>) {
print $line if $line =~ /pattern/;
}
close $fh;
}
Note that I skip entries which aren't regular files (not -f
, see filetest operators), and use lexical filehandes instead of typeglobs (my $fh
instead of FILE
).
Another option for getting lists of entries is glob (see File::Glob), which does return the path:
my @entries = glob $dir; # dir1/dir2/file.txt (etc)
But if you are indeed acquiring the whole filelist ahead, as opposed to reading and processing an item at a time, then you might as well filter it as needed right away, so
my @files = grep { -f } glob $dir;
where you now have only regular files.
The whole list can be filtered also in the program above of course. Can do it in the map
itself
opendir (my $dh, $dir) or die $!;
my @files = map { -f "$dir/$_" ? "$dir/$_" : () } readdir $dh;
closedir $dh;
Here map
works as a filter as well by a little trick: An empty list ()
, returned when an entry isn't a plain file, gets flattened in the returned list thus effectively disappearing, so this amounts to not returning anything in that case. Or, chain grep
to map
as usual
my @files = grep { -f } map { "$dir/$_" } readdir $dh;
This does make (yet) another pass over the list, but that is optimized and hardly ever a concern (unless we had huge filelists -- what poses problems in its own right -- and were doing this very often).
I'd like to also mention that there are good modules for this, as well.
An example with Path::Tiny
use Path::Tiny;
my $it = path($dir)->iterator;
while (my $entry = $it->()) {
# it omits . and ..
}
This is a "lazy" iterator. See docs, and more so for the many other things that this module offers.
† Instead of that file.txt
Upvotes: 2