I would like to count the number of files inside a folder with Perl. With the following code I can list them, but how can I count them in Perl?
$dir = "/home/Enric/gfs-0.5.2016061400";
opendir(DIR, "$dir");
@FILES = grep { /gfs./ } readdir(DIR);
foreach $file (@FILES) {
print $file, "\n";
}
closedir(DIR);
Browse to the folder containing the files you want to count. Highlight one of the files in that folder and press the keyboard shortcut Ctrl + A to highlight all files and folders in that folder. In the Explorer status bar, you'll see how many files and folders are highlighted, as shown in the picture below.
The easiest way to count files in a directory on Linux is to use the “ls” command and pipe it with the “wc -l” command. The “wc” command is used on Linux in order to print the bytes, characters or newlines count. However, in this case, we are using this command to count the number of files in a directory.
If you want to just count them, once you have a directory open for reading you can manipulate context so that readdir returns the list of all entries but then assign that to a scalar. This gives you the length of the list, ie. the number of elements
opendir my $dh, $dir;
my $num_entries = () = readdir($dh);
The construct = () =
imposes list context on readdir
and assigns (that expression†) to a scalar, which thus gets the number of elements in that list.‡ § See it in perlsecret. Also see this page.
There are clearer ways, of course, as below.
If you want to count certain kinds of files, pass the file list through grep first, like you do. Since grep
imposes the list context on its input readdir
returns the list of all files, and after filtering grep
itself returns a list. When you assign that to a scalar you get the length of that list (number of elements), ie. your count. For example, for all regular files and /gfs./
files
use warnings;
use strict;
my $dir = '/home/Enric/gfs-0.5.2016061400';
opendir my $dh, $dir or die "Can't open $dir: $!";
my $num_files = grep { -f "$dir/$_" } readdir($dh);
rewinddir($dh); # so that it can read the dir again
my $num_gfs = grep { /gfs./ } readdir($dh);
(This is only an example, with rewinddir
so that it works as it stands. To really get two kinds of files from a directory better iterate over the entries one at a time and sort them out in the process, or read all files into an array and then process that)
Note that readdir
returns the bare filename, without any path. So for most of what is normally done with files we need to prepend it with the path (unless you first chdir
to that directory). This is what is done in the grep
block above so that the -f
file test (-X) has the correct filename.
If you need to use the file list itself, get that into an array and then assign it to a scalar
# Get the file list, then its length
my @files_gfs = map { "$dir/$_" } grep { /gfs./ } readdir($dh);
my $num_gfs = @files_gfs;
Here map builds the full path for each file. If you don't need the path drop map { }
. Note that there is normally no need for the formal use of scalar on the array to get the count, like
my $num_gfs = scalar @files_gfs; # no need for "scalar" here!
Instead, simply assign an array to a scalar instead, it's an idiom (to say the least).
If you are processing files as you read, count as you go
my $cnt_gfs = 0;
while (my $filename = readdir($dh)) {
$cnt_gfs++ if $filename =~ /gfs./;
# Process $dir/$filename as needed
}
Here readdir
is in the scalar context (since its output is assigned to a scalar), and it iterates through the directory entries, returning one at a time.
A few notes
In all code above I use the example from the question, /gfs./
-- but if that is in fact meant to signify a literal period then it should be replaced by /gfs\./
All this talk about how readdir
returns bare filename (no path) would not be needed with glob (or then better File::Glob), which does return the full path
use File::Glob ':bsd_glob'; # (better with this)
my @files = glob "$dir/*";
This returns the list of files with the path $dir/filename
.
Not that there is anything wrong with opendir
+readdir
. Just don't forget the path.
Yet another option is to use libraries, like Path::Tiny with its children method.
† The assignment () = readdir $dh
itself returns a value as well, and in this case that whole expression (the assignment) is placed in the scalar context.
‡ The problem is that many facilities in Perl depend in their operation and return on context so one cannot always merely assign what would be a list to a scalar and expect to get the length of the list. The readdir
is a good example, returning a list of all entries in list context but a single entry in scalar context.
§ Here is another trick for it
my $num_entries = @{ [ readdir $dh ] };
Here it is the constructor for an anonymous array (reference), []
, which imposes the list context on readdir
, while the dereferencing @{ }
doesn't care about context and simply returns the list of elements of that arrayref. So we can assign that to a scalar and such scalar assignment returns the number of elements in that list.
You have the list of files in @FILES
. So your question becomes "how do I get the length of an array?" And that's simple, you simply evaluate the array in scalar context.
my $number_of_files = @FILES;
print $number_of_files;
Or you can eliminate the unnecessary scalar variable by using the scalar()
function.
print scalar @FILES;
Try this code for starters (this is on Windows and will include .
, ..
and folders. Those can be filtered out if you want only files):
#!/usr/bin/perl -w
my $dirname = "C:/Perl_Code";
my $filecnt = 0;
opendir (DIR, $dirname) || die "Error while opening dir $dirname: $!\n";
while(my $filename = readdir(DIR)){
print("$filename\n");
$filecnt++;
}
closedir(DIR);
print "Files in $dirname : $filecnt\n";
exit;
I know this isn't in Perl, but if you ever need a quick way, just type this into bash command line:
ls -1 | wc -l
ls -1
gives you a list of the files in the directory, and wc -l
gives you the line count. Combined, they'll give you the number of files in your directory.
Alternatively, you can call bash from Perl (although you probably shouldn't), using
system("ls -1 | wc -l");
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With