windows - 如何从 Perl 创建然后使用长 Windows 路径?

标签 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

同样,一旦绝对路径超过 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 issuesWin32API::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/

相关文章:

Python 日志记录适用于 Windows,但不适用于 Mac OS

linux - 谁能解释一下 Shell::Source perl 模块或 Shell::GetEnv 模块的用法

linux - 有没有办法让 Windows 像 Linux 那样返回目录的路径?

c - 为什么我不能声明一个已经定义了结构的变量?

perl - 确定目录的用户/组所有权

c# - 如何将字符串添加到 string[] 数组?没有 .Add 功能

path - Windows 10 中是否仍然存在 MAX_PATH 问题

r - 将非 8dot3 目录路径的短名称扩展为长名称

python - 如何在 Windows 中的 python 3 中正确地将焦点传递给消息框或从消息框传递焦点?

perl - 如何复制除 Perl 中所有隐藏文件之外的目录?