Sorting directory filenames with Perl
Solution 1
You're constructing a series of one-element lists, sorting each (which is a no-op) and printing it. What you want to do is read the entire list of files into one list and then sort it, like this:
my $file;
my @files;
opendir (DIR, "$dir");
while ($file = readdir(DIR)) {
push (@files, $file);
}
@files = sort {$a cmp $b} @files;
foreach $file (@files) {
print "$file\n";
}
Solution 2
Another way, using File::Slurp
use warnings;
use strict;
use File::Slurp;
use Data::Dumper;
my @files = read_dir($dir);
@files = sort @files;
print Dumper(\@files);
This takes care of opening and closing the directory, checking for success, and automatically excluding the special . and .. directories, which you probably don't want.
Solution 3
my @files
within the lexical scoping by the while
loop will always result in creating a new @files
array on each iteration of the loop. Hence at any time, @files
will contain only a single element and sorting is thus meaningless. Now see Anomie's answer.
Solution 4
Seriously, you are doing a lot more work than you have to.
use warnings;
use strict;
use autodie;
use File::Spec::Functions qw'no_upwards';
my $dir = '.';
opendir my($dh), $dir;
for my $file ( no_upwards sort readdir $dh ){
print "$file\n";
}
closedir $dh;
Solution 5
Sorting paths is tricky because, in the ASCII collating sequence, the path delimiter, slash (/) is before most path characters, but not before all, most notably dot and dash.
Break the paths up into path elements by splitting on slash. Compare path elements alphanumerically with cmp. If it's a tie, then the path with fewer elements comes before the path with more elements.
Be sure to chomp off any newlines. Use &bypath subroutine with Perl sort command: sort bypath @files;
sub bypath {
my @a = split m'/', $a;
my @b = split m'/', $b;
for ( my $i = 0; $i<=$#a; $i++ ) {
last if $i > $#b;
return $a[$i] cmp $b[$i] if $a[$i] cmp $b[$i];
}
return $#a <=> $#b;
}
Sample results:
- this
- this/that
- this/that/other
- this/that/other/filea
- this/that/other/fileb
- this/that/other.new/filea
Related videos on Youtube
superfry
Updated on June 04, 2022Comments
-
superfry almost 2 years
I want to sort/print the files in a directory by name. My code lists them all, but the sorting is skewed. Here is my code and results. Any suggestions will be most welcomed!
my $file; opendir (DIR, "$dir"); while ($file = readdir(DIR)) { push (my @files, $file); @files = sort {$a cmp $b} @files; #NOT sorting! foreach $file (@files) { print "$file\n"; } }
And here are the "sorted" results:
Screenshot-Chess_-_Human_versus_GNUchess.png test.html katyperry.gif test.cgi Californication.S04E05.HDTV.XviD-ASAP.avi FreeWatch_13.exe proxy.jpg test.pl- . attachment2.jpg attachment.jpg Californication.S04E06.HDTV.XviD-LOL.avi Californication.S04E07.HDTV.XviD-LOL.avi boxter.jpg ..
-
Andy Lester about 13 yearsBetter yet, replace that
while
loop with@files = readdir(DIR)
. -
Anomie about 13 years@andy-lester: For that matter,
print join("\n", sort { $a cmp $b } readdir(DIR))."\n";
-
superfry about 13 yearsThanks! I cannot believe that I missed something so fundamental. Thanks to StackOverflow and all of those who assisted with my problem!
-
superfry about 13 yearsAlso adding the
lc
function avoids weird results due to name capitalization: @files = sort {lc($a) cmp lc($b)} @files;