Sorting directory filenames with Perl

10,888

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
Share:
10,888

Related videos on Youtube

superfry
Author by

superfry

Updated on June 04, 2022

Comments

  • superfry
    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
    Andy Lester about 13 years
    Better yet, replace that while loop with @files = readdir(DIR).
  • Anomie
    Anomie about 13 years
    @andy-lester: For that matter, print join("\n", sort { $a cmp $b } readdir(DIR))."\n";
  • superfry
    superfry about 13 years
    Thanks! I cannot believe that I missed something so fundamental. Thanks to StackOverflow and all of those who assisted with my problem!
  • superfry
    superfry about 13 years
    Also adding the lc function avoids weird results due to name capitalization: @files = sort {lc($a) cmp lc($b)} @files;