Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I find the case-preserved name of a file in Perl?

On a case-insensitive file system, such as NTFS or HFS+, given the name of a file, what is the most efficient way to determine the case-preserved version of the file name?

Consider on HFS+ (Mac OS X):

> perl -E 'say "yes" if -e "/TMP"'
yes

It says it exists, of course, but I have no idea how its case is preserved. What's the most efficient way to determine the actual case?

What I've tried so far:

  • glob with character classes: It doesn't work on Windows:

    > perl -E "say for glob "C:\\Perl"
    C:\Perl
    > perl -E "say for glob "C:\\[Pp][Ee][Rr][Ll]"
    

    Note the lack of output from that last command. :-(

  • opendir/readdir: Works, but seems rather inefficient to read an entire directory:

      > perl -E "opendir my $dh, 'C:\\'; say for grep { lc $_ eq 'perl' } readdir $dh; close $dh"
    Perl
    

Is it crazy to think that there ought to be some core operating system instructions or something to get at this information more efficiently?

like image 582
theory Avatar asked Feb 19 '15 17:02

theory


2 Answers

The opendir/readdir/grep solution is the proper one. Via Twitter, Neil Bowers points to this quotation from perlport:

Don't count on filename globbing. Use opendir, readdir, and closedir instead.

@miyagawa, also via Twitter, says that there is no system call for this, and if there was, it wouldn't be portable.

And given that @mob's answer and comments from David Golden suggest that glob would be more expensive than opendir, readdir, anyway, there just seems to be no other way around it.

So here's the function I wrote to find all the cases for a given basename in a directory:

sub _actual_filenames {
    my $dir = shift;
    my $fn = lc shift;
    opendir my $dh, $dir or return;
    return map { File::Spec->catdir($dir, $_) }
        grep { lc $_  eq $fn } readdir $dh;
}
like image 143
theory Avatar answered Oct 16 '22 19:10

theory


On Windows,

>perl -MWin32 -E"say Win32::GetLongPathName($ARGV[0])" "C:\PROGRAM FILES"
C:\Program Files

>perl -MWin32 -E"say Win32::GetLongPathName($ARGV[0])" C:\PROGRA~1
C:\Program Files

On unix, fcntl's F_GETPATH function will do.

like image 31
ikegami Avatar answered Oct 16 '22 20:10

ikegami