如何创建然后使用Perl的长Windows路径?

bri*_*foy 19 windows perl long-filenames

我有一个构建过程的一部分,在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
Run Code Online (Sandbox Code Playgroud)

类似地,一旦绝对路径结束,手动构建每个目录级别就会失败MAX_PATH.

这不是新的,不是Perl的错,而且Microsoft在命名文件,路径和命名空间中记录它.他们的修复建议\\?\在任何路径前添加访问Unicode文件名API.但是,这似乎不是Perl脚本的完整修复,因为它仍然失败:

 use File::Path qw( make_path );

 make_path( '\\\\?\\C:\\.....' );  # still fails if path is over MAX_PATH, works otherwise
Run Code Online (Sandbox Code Playgroud)

这可能是因为make_path拉开了它的参数,然后一次一个级别地遍历目录,所以\\?\只适用于顶层,这是在其中MAX_PATH.

向ActiveState挖出了一个错误报告,表明我还需要修改一些其他内容来获取Unicode文件名,而Jan Dubois在Re 2:Windows 2K/XP上的"长"文件名中提供了更多细节,尽管我是不确定它是否适用(并且非常古老).perlrun提到这个用途是转换的工作-C,但显然这部分被放弃了.perl RT队列有一个更新的错误60888:Win32:支持文件名中的完整unicode(使用Wide-system调用).

Miyagawa 注意到一些Unicode文件名问题Win32API :: File没有特别提到长路径.但是,Win32API :: File CPAN论坛条目似乎只表示恐惧,这会导致愤怒,导致仇恨,等等.在Perlmonks帖子中有一个例子如何在Windows中使用Unicode(UTF16-LE)文件名统计文件?.这似乎Win32::CreateDirectory是答案,我下次在Windows机器旁边尝试时会尝试.

然后,假设我可以创建长路径路径.现在我必须教它Module :: Build,也许还有其他东西来处理它.对于monkeypatches来说,这可能会很容易,如果Win32::GetANSIPathName()它在锡上说的话.

Sin*_*nür 9

进展:

以下脚本有效:它将字符串写入具有长路径的目录中的文件,并且能够读回相同的字符串.(成功运行不会产生控制台输出).我也做了一个繁琐的努力来覆盖open.

#!/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;
}
Run Code Online (Sandbox Code Playgroud)

使用Win32::GetANSIPathName()内置功能open不起作用:返回的路径太长.

请参阅编辑失败实验的历史记录

  • 好的,这是我认为的答案的一半.我还需要一个表单中的路径,程序的其余部分可以在没有杂技的情况下使用.我无法为此修补大部分CPAN工具链.:) (2认同)

n0r*_*0rd 6

以下代码实际上创建了相当深(超过260个字符长)的目录结构.至少在我的机器上:

use Win32::API;

$cd = Win32::API->new('kernel32', 'CreateDirectoryW', 'PP', 'N');

$dir = '\\\\?\\c:\\!experiments';

$res = 1;

do
{
    print 'path length: ' . length($dir) . "\n";
    $dirname = pack('S*', unpack('C*', "$dir\0"));  #dirty way to produce UTF-16LE string

    $res = $cd->Call($dirname, 0);
    print "$res\n";

    $dir .= '\\abcde';

} while ( $res );
Run Code Online (Sandbox Code Playgroud)