Count number of files in a folder with Perl
Asked Answered
B

5

5

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);
Benares answered 14/6, 2016 at 20:32 Comment(7)
As always with perl add use strict; use warnings; to the top. $#FILES is one way to have the count of files but as arrays start at 0 you need to add 1 to itDakar
Possible duplicate of Find size of an array in PerlScheldt
Did you write this code yourself, or did you copy it from somewhere else? The reason I ask is because there are many things wrong with it, and if you copied it from a tutorials site or something, I would recommend avoiding that site in the future.Scheldt
@FILES = grep { /gfs./ } readdir(DIR); print join "\n", scalar(@FILES);Blinkers
@KeepCalmAndCarryOn: Using $#array for anything other than getting the last index in @array is complicating things unnecessarily. Why not just use @array in scalar context?Itch
@ssr1012: Your use of join() seems unnecessary. Why not just print scalar(@FILES), "\n";?Itch
@Dave Cross: yes. your correct.Blinkers
L
14

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.

Luminesce answered 14/6, 2016 at 23:5 Comment(0)
I
6

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;
Itch answered 15/6, 2016 at 9:59 Comment(1)
Thank you for the comments @Dave CrossBenares
J
1

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;
Jaipur answered 14/6, 2016 at 21:54 Comment(0)
S
1

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"); 
Stefanistefania answered 15/6, 2016 at 21:23 Comment(0)
A
0

As a variation on zdim's answer, let's say you don't have a directory handle opened already. In that case

my $num_files =()= glob("$dir/*");

might be more useful. This works on the same principle as zdim's suggestion, but without the need for the opendir call first.

This returns the number of files without including . and .., at least on Linux.

Aesop answered 27/7, 2023 at 1:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.