现有答案的重点是解释为什么 -e 在 OSX 上不区分大小写地工作,以及为什么它这样做是有充分理由的。
但是,有时您确实想知道路径是否以大小写精确的形式存在,因为即使 文件系统 可能不关心,其他软件 可能。
Perl 本身就是一个例子:
在 OSX 上,Perl(不幸的是)接受带有 use 语句的模块名称的大小写变体,因为 use 执行文件系统查找,这本质上是不区分大小写的。但是,实际上 using 该模块将不起作用,除非模块名称是 case-EXACT;例如:perl -MData::Dumper -E 'print Dumper(666)'(OK)vs.perl -Mdata::dumper -E 'print Dumper(666)'(中断,因为即使加载模块看似成功,它的元素,例如Dumper(),也无法访问)。
问题简而言之:如果路径 /FOO/BAR 存在于您的 HFS+ 文件系统中(在 OSX 上),
-e '/foo/bar' 或其任何大小写变化都将表明该路径存在,并且没有简单的方法来确定是否存在/foo/bar 是一个大小写精确的表示,或者更一般地说,是路径的真实大小写。
这是一个使用以下方法的简单实现:
- 利用
File::Glob::bsd_glob() 默认情况下区分大小写这一事实,即使在 OSX 上也是如此,其文件系统 (HFS+) 默认情况下不区分大小写。
- 但是,匹配只发生在包含实际 pattern 的路径组件上,而 literal 名称按原样返回。
- 因此,从输入路径构造一个 glob,每个
通过将第一个字符括起来,将路径组件转换为自匹配模式。 [] 中的每个路径组件(仅匹配该字符本身的集合)。
sub istruecasepath {
use File::Glob qw/bsd_glob/;
return defined bsd_glob join '', map {
m`^(//?|\.|\.\.|)$` ?
$_
:
'[' . substr($_, 0, 1) . ']' . substr($_, 1)
} split m`(//?)`, shift;
}
这适用于以下限制:
- 输入路径不得包含非 ASCII 字符
- 输入路径不得包含模式元字符(即路径组件不得包含文字字符。例如
* 或 [ 可能会被误解为模式(通配符)字符。
这是一个稳健的实现,它需要额外的工作来克服这些限制;输入应为 Unicode 字符串:
sub istruecasepath {
use File::Glob qw/:bsd_glob/;
use Unicode::Normalize;
# Convert to NFD Unicode normal form, because that's how HFS+ stores names.
my $path_nfd_quoted = NFD shift;
# \-quote glob characters and '\' instances.
$path_nfd_quoted =~ s/[][{}*?\\]/\\$&/; # /
my $glob = join '', map {
m`^(//?|\.|\.\.|)$` ?
$_
:
do { my $len = m`^\\` ? 2 : 1; '[' . substr($_, 0, $len) . ']' . substr($_, $len) }
} split m`(//?)`, $path_nfd_quoted;
return defined bsd_glob "$glob", GLOB_QUOTE;
}
作为奖励,这里有一个变体,返回文件系统中存储的大小写精确路径,给定一个大小写变化:
sub getruecasepath {
use File::Glob qw/:bsd_glob/;
use Unicode::Normalize;
# Convert to NFD Unicode normal form, because that's how HFS+ stores names.
my $path_nfd_quoted = NFD shift;
# \-quote glob characters and '\' instances.
$path_nfd_quoted =~ s/[][{}*?\\]/\\$&/; # /
my $glob = join '', map {
m`^(//?|\.|\.\.|)$` ?
$_
:
do { my $len = m`^\\` ? 2 : 1; '[' . substr($_, 0, $len) . ']' . substr($_, $len) }
} split m`(//?)`, $path_nfd_quoted;
my $path_truecase = bsd_glob "$glob", GLOB_NOCASE | GLOB_QUOTE;
return if not defined $path_truecase;
# !! bsd_glob() returns a raw UTF8-encoded byte sequence that isn't
# !! automatically reconverted to a Unicode string, so we must do it
# !! manually here.
utf8::decode($path_truecase);
return $path_truecase;
}