如何在 Perl 中检测不区分大小写的文件系统?

How do I detect a case-insensitive file system in Perl?

我尝试使用 File::Spec->case_tolerant, but it returns false on HFS+, which is wrong. I suspect it's because File::Spec::Unix 总是 returns 错误。我目前的解决方法是这个函数:

my $IS_CASE_INSENSITIVE;
sub _is_case_insensitive {
    unless (defined $IS_CASE_INSENSITIVE) {
        $IS_CASE_INSENSITIVE = 0;
        my ($uc) = glob uc __FILE__;
        if ($uc) {
            my ($lc) = glob lc __FILE__;
            $IS_CASE_INSENSITIVE = 1 if $lc;
        }
    }
    return $IS_CASE_INSENSITIVE;
}

但这是一个 hack,因为:1) 在区分大小写的文件系统上,这两个文件可能都存在;和 2) 不同的卷可以有不同的文件系统。

我建议您使用核心 File::Temp 模块创建一个名称中包含小写字符的新的唯一文件。该文件设置为在对象被销毁时被删除,如果不是之前,也就是子程序退出时。

如果按大写文件名访问时文件不存在则文件系统区分大小写.

如果大写名称确实存在那么我们必须检查我们没有碰巧遇到一个大写版本已经存在的文件,所以我们删除文件。如果大写条目现在已经消失,那么文件系统是不区分大小写

如果大写名称仍然存在,那么它是一个在我们创建临时文件之前就存在的文件。我们只是循环并创建一个具有不同名称的新临时文件,尽管发生这种情况的可能性非常小。如果您愿意,可以通过为 SUFFIX 使用一个古怪的值来进一步减少这种可能性。请注意,您使用的字符在您希望测试的任何文件系统上都有效。

我已经在 Windows 7 和 Ubuntu 上测试过了。

use strict;
use warnings;
use 5.010;
use autodie;

use File::Temp ();

printf "File system %s case_insensitive\n", case_insensitive() ? "is" : "isn't";


sub case_insensitive {

  while () {

    my $tmp = File::Temp->new(
      TEMPLATE => 'tempXXXXXX',
      SUFFIX   => '.tmp',
      UNLINK   => 1,
    );

    my $uc_filename = uc $tmp->filename;

    return 0 if not -e $uc_filename;
    $tmp = undef;
    return 1 if not -e $uc_filename;
  }
}

事实上,每个考虑的目录都必须单独检查。这是因为,在类 Unix 系统上,any 目录可以是与其他目录不同的文件系统。此外,使用 glob 不是很可靠;来自 perlport:

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

但我认为 @borodin-e 的使用有关。所以这里有一个函数,它使用 -e 来确定指定的目录是否在不区分大小写的文件系统上:

my %IS_CASE_INSENSITIVE;
sub is_case_insensitive {
    my $dir = shift;
    unless (defined $IS_CASE_INSENSITIVE{$dir}) {
        $IS_CASE_INSENSITIVE{$dir} = -e uc $dir && -e lc $dir;
    }
    return $IS_CASE_INSENSITIVE{$dir};
}

您可以为 Windows 添加一些试探法来仅缓存驱动器号的值,因为它定义了一个挂载点。当然,如果目录的大写和小写变体都存在,它将在区分大小写的文件系统上失败。但除此之外,除非有其他方法可以更全面地告诉哪些目录与哪些挂载点匹配,否则您必须检查任何目录。