我有一个构建过程的一部分,它在 Windows 中创建了一条可怕的长路径。这不是我的错。好几个目录,目录名都没有异常长;它们的长度和数量足以使其超过 MAX_PATH
(260 个字符)。除了 ASCII,我没有在这些名称中使用任何其他名称。
最大的问题是爆炸发生在 Module::Build 的内心深处在 dist
目标期间,虽然我认为构建系统并不重要,因为它们会创建相同的目录。
使用 File::Path
创建这些超长目录之一失败:
use File::Path qw( make_path );
make_path( 'C:\\.....' ); # fails if path is over 260 chars
同样,一旦绝对路径超过 MAX_PATH
,手动构建每个目录级别都会失败。
这不是新的,不是 Perl 的错,Microsoft 在 Naming Files, Paths, and Namespaces 中记录了它.他们的修复建议在任何访问 Unicode 文件名 API 的路径前添加 \\?\
。然而,这似乎并不是 Perl 脚本的完整修复,因为它仍然失败:
use File::Path qw( make_path );
make_path( '\\\\?\\C:\\.....' ); # still fails if path is over MAX_PATH, works otherwise
这可能是因为 make_path
将其参数分开,然后逐级遍历目录,因此 \\?\
仅适用于顶层,它位于 MAX_PATH
内。
我挖出了一个bug report to ActiveState这表明我还需要修复一些其他内容才能获得 Unicode 文件名,Jan Dubois 在 Re: "long" filenames on Windows 2K/XP 中提供了更多详细信息,虽然我不确定它是否适用(并且非常旧)。 perlrun提到这曾经是 -C
开关的工作,但显然那部分被放弃了。 perl RT 队列最近有一个错误 60888: Win32: support full unicode in filenames (use Wide-system calls) .
宫川 notes some Unicode filename issues和 Win32API::File没有特别提到长路径。然而,Win32API::File CPAN Forum entry似乎只表示恐惧,恐惧导致愤怒,愤怒导致仇恨,等等。 Perlmonks 中有一个示例发布How to stat a file with a Unicode (UTF16-LE) filename in Windows? . Win32::CreateDirectory
似乎就是答案,下次我会在 Windows 机器旁边尝试一下。
然后,假设我可以创建长路径路径。现在我必须教 Module::Build 和其他东西来处理它。如果 Win32::GetANSIPathName()
按照锡 jar 上的说明进行操作,那么使用 monkeypatches 可能会立即变得容易。
最佳答案
对于需要处理字符串的每个函数,Windows 都有两个单独的系统调用,一个使用 ANSI 又名事件代码页作为编码(例如 cp1252)的“A”调用和一个使用 UTF-16le 的“W”调用。 Perl 使用“A”调用,而 \\?\
仅适用于“W”调用。
您可以使用 Win32::API 访问“W”调用,如下面的脚本所示,但 Win32::LongPath 不仅使用“W”调用,而且会自动添加 \\?\
!
使用 Win32::API 调用 CreateDirectoryW
以使用长路径(\\?\
-前缀路径)的示例:
#!/usr/bin/perl
use strict;
use warnings;
use Carp;
use Encode qw( encode );
use Symbol;
use Win32;
use Win32API::File qw(
CreateFileW OsFHandleOpen
FILE_GENERIC_READ FILE_GENERIC_WRITE
OPEN_EXISTING CREATE_ALWAYS FILE_SHARE_READ
);
use Win32::API;
use File::Spec::Functions qw(catfile);
Win32::API->Import(
Kernel32 => qq{BOOL CreateDirectoryW(LPWSTR lpPathNameW, VOID *p)}
);
my %modes = (
'<' => {
access => FILE_GENERIC_READ,
create => OPEN_EXISTING,
mode => 'r',
},
'>' => {
access => FILE_GENERIC_WRITE,
create => CREATE_ALWAYS,
mode => 'w',
},
# and the rest ...
);
use ex::override open => sub(*;$@) {
$_[0] = gensym;
my %mode = %{ $modes{$_[1]} };
my $os_fh = CreateFileW(
encode('UCS-2le', "$_[2]\0"),
$mode{access},
FILE_SHARE_READ,
[],
$mode{create},
0,
[],
) or do {$! = $^E; return };
OsFHandleOpen($_[0], $os_fh, $mode{mode}) or return;
return 1;
};
my $path = '\\\\?\\' . Win32::GetLongPathName($ENV{TEMP});
my @comps = ('0123456789') x 30;
my $dir = mk_long_dir($path, \@comps);
my $file = 'test.txt';
my $str = "This is a test\n";
write_test_file($dir, $file, $str);
$str eq read_test_file($dir, $file) or die "Read failure\n";
sub write_test_file {
my ($dir, $file, $str) = @_,
my $path = catfile $dir, $file;
open my $fh, '>', $path
or croak "Cannot open '$path':$!";
print $fh $str or die "Cannot print: $!";
close $fh or die "Cannot close: $!";
return;
}
sub read_test_file {
my ($dir, $file) = @_,
my $path = catfile $dir, $file;
open my $fh, '<', $path
or croak "Cannot open '$path': $!";
my $contents = do { local $/; <$fh> };
close $fh or die "Cannot close: $!";
return $contents;
}
sub mk_long_dir {
my ($path, $comps) = @_;
for my $comp ( @$comps ) {
$path = catfile $path, $comp;
my $ucs_path = encode('UCS-2le', "$path\0");
CreateDirectoryW($ucs_path, undef)
or croak "Failed to create directory: '$path': $^E";
}
return $path;
}
关于windows - 如何从 Perl 创建然后使用长 Windows 路径?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/1721807/