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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1721807/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-15 13:25:53  来源:igfitidea点击:

How do I create then use long Windows paths from Perl?

windowsperllong-filenames

提问by brian d foy

I have part of a build process that creates a hideously long paths in Windows. It's not my fault. It's several directories deep, and none of the directory names are abnormally long; they're just long and numerous enough to make it over MAX_PATH(260 chars). I'm not using anything other than ASCII in these names.

我有一个构建过程的一部分,该过程在 Windows 中创建了一条长得可怕的路径。这不是我的错。有好几个目录,目录名都没有异常长;它们的长度和数量足以让它结束MAX_PATH(260 个字符)。在这些名称中,我没有使用除 ASCII 以外的任何内容。

The big problem is that the blow-up happens deep in the guts of Module::Buildduring the disttarget, although I figure the build system doesn't matter because they'd make the same directories.

最大的问题是,在目标期间,在Module::Build 内部深处发生爆炸dist,尽管我认为构建系统并不重要,因为它们会创建相同的目录。

Creating one of these overly-long directories with File::Pathfails:

创建这些过长目录之一File::Path失败:

 use File::Path qw( make_path );

 make_path( 'C:\.....' ); # fails if path is over 260 chars

Similarly, constructing each directory level by hand fails once the absolute path would go over MAX_PATH.

同样,一旦绝对路径超过 ,手动构建每个目录级别就会失败MAX_PATH

This isn't new, isn't Perl's fault, and Microsoft documents it in Naming Files, Paths, and Namespaces. Their fix suggests adding the \\?\in front of any path to access the Unicode filename API. However, that doesn't seem to be the full fix for a Perl script because it still fails:

这并不新鲜,也不是 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

This might be because make_pathpulls apart its argument and then goes through the directories one level at a time, so \\?\only applies to the top-level, which is within MAX_PATH.

这可能是因为make_path将其参数分开,然后一次遍历一个目录,因此\\?\仅适用于顶级,即MAX_PATH.

I dug up a bug report to ActiveStatethat suggests there's something else I need to fix up to get to the Unicode filenames, and Jan Dubois gives a bit more details in Re: "long" filenames on Windows 2K/XP, although I'm not sure it applies (and is extremely old). perlrunmentions that this use to be the job of the -Cswitch, but apparently that part was abandoned. The perl RT queue has a more recent bug 60888: Win32: support full unicode in filenames (use Wide-system calls).

向 ActiveState挖掘了一个错误报告,表明我需要修复其他一些东西才能获得 Unicode 文件名,Jan Dubois 在Re: "long" filenames on Windows 2K/XP 中提供了更多详细信息,尽管我是不确定它是否适用(并且非常旧)。perlrun提到这-C曾经是 switch的工作,但显然那部分被放弃了。perl RT 队列有一个更新的错误60888:Win32:支持文件名中的完整 unicode(使用宽系统调用)

Miyagawa notes some Unicode filename issuesand Win32API::Filewithout specifically mentioning long paths. However, the Win32API::File CPAN Forum entryseems to indicate only fear, which leads to anger, which leads to hate, and so on. There's an example in the Perlmonkspost How to stat a file with a Unicode (UTF16-LE) filename in Windows?. It seems the Win32::CreateDirectoryis the answer, and I'll try that the next time I get next to a Windows machine.

Miyagawa指出了一些 Unicode 文件名问题Win32API::File,但没有特别提到长路径。但是,Win32API::File CPAN 论坛条目似乎仅表示恐惧,从而导致愤怒,导致仇恨等等。Perlmonks 的帖子How to stat a file with a Unicode (UTF16-LE) filename in Windows? 中有一个例子. 似乎这Win32::CreateDirectory就是答案,下次我靠近 Windows 机器时,我会尝试这样做。

Then, supposing I can create the long path path. Now I have to teach Module::Build, and maybe other things, to handle it. That might be immediately easy with monkeypatches if Win32::GetANSIPathName()does what it says on the tin.

然后,假设我可以创建长路径路径。现在我必须教 Module::Build,也许还有其他东西,来处理它。如果Win32::GetANSIPathName()按照罐头上的说明进行操作,使用monkeypatches 可能会立即变得容易。

采纳答案by Sinan ünür

Progress:

进步:

The following script works: It writes a string to a file in a directory with a long path and it is able to read back the same string. (A successful run produces no console output). I have also made a cumbersome effort to override open.

以下脚本有效:它将字符串写入具有长路径的目录中的文件,并且能够读回相同的字符串。(成功运行不会产生控制台输出)。我也做了一个繁琐的努力来覆盖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]
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
C:\> net share
perlbuild    e:\home\src
")); #dirty way to produce UTF-16LE string $res = $cd->Call($dirname, 0); print "$res\n"; $dir .= '\abcde'; } while ( $res );
"), $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
#!/usr/bin/perl

use strict;
use warnings;

use File::Path qw(make_path);
use File::Slurp;
use Path::Class;

my $top = dir('//Computer/perlbuild');
my @comps = ('0123456789') x 30;

my $path = dir($top, @comps);

make_path $path, { verbose => 1 };

my $file = file($path, 'test.txt');

write_file "$file" => 'This is a test';

print read_file "$file";
"); CreateDirectoryW($ucs_path, undef) or croak "Failed to create directory: '$path': $^E"; } return $path; }

Using Win32::GetANSIPathName()with built-in opendoes not work: The path returned is too long.

Win32::GetANSIPathName()与内置一起使用open不起作用:返回的路径太长。

See edit history for failed experiments.

查看失败实验的编辑历史记录。

回答by n0rd

Following code actually creates quite deep (more than 260 characters long) directory structure. At least on my machine:

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

mkdir \Computer\perlbuild2345678923456789234567892345678923456
78923456789234567892345678923456789234567892345678923456789
23456789234567892345678923456789234567892345678923456789
2345678923456789: No such file or directory; The filename or extension is too
 long at C:\Temp\k.pl line 15

回答by Sinan ünür

I understand this is not a solution to your specific problem. However, there are a lot of scenarios where being able to map a very long path to a drive-letter would allow one to sidestep the issue and would therefore be useful in dealing with very long path names without having to wade through a whole lot of Windows specific code and docs.

我知道这不是您特定问题的解决方案。但是,在很多情况下,能够将很长的路径映射到驱动器号将允许人们回避这个问题,因此在处理很长的路径名时很有用,而无需涉足一大堆Windows 特定的代码和文档。

Despite all the effort I put into figuring out how to do this, I am going to recommend somehow using SUBST. Win32::FileOpprovides Substand Unsubst. You can then map the top level working directory to an unused drive letter (which you can find by using Substed). I would start checking with Zand working backwards.

尽管我付出了很多努力来弄清楚如何做到这一点,但我还是建议以某种方式使用SUBST. Win32::FileOp提供SubstUnsubst. 然后,您可以将顶级工作目录映射到未使用的驱动器号(您可以使用 找到Substed)。我会开始检查Z并向后工作。

Or, you can shell out, invoke substutility with no parameters to get a list of current substitutions, choose one that is not there.

或者,您可以退出,调用subst不带参数的实用程序以获取当前替换的列表,选择一个不存在的替换。

None of this is entirely safe as substitutions could change during the build process.

所有这些都不是完全安全的,因为在构建过程中替换可能会发生变化。

回答by Sinan ünür

This should really be a comment but posting code in comments is hardly useful.

这真的应该是一条评论,但在评论中发布代码几乎没有用。

UNC paths do not work either:

UNC 路径也不起作用:

##代码## ##代码##

Result:

结果:

##代码##

回答by mob

I had three thoughts, all of them kind of hacks:

我有三个想法,所有这些想法都是黑客:

  1. Start with some short directory names (C:\data_directory\a\b\c\d\4\5\6\...) and then rename the directories (starting with the deepest directory first of course).

  2. Create Windows shortcut to a moderately long path and create files and subdirectories from there? (Or install Cygwin and use symlinks?)

  3. Create the desired files in a directory with a short name, zip/tar them, and unpack them to the directory with the longer name. Or create zip/tar files "by hand" and unpack them in the desired location.

  1. 从一些短目录名(C:\data_directory\a\b\c\d\4\5\6\...)开始,然后重命名目录(当然首先从最深的目录开始)。

  2. 为中等长度的路径创建 Windows 快捷方式并从那里创建文件和子目录?(或者安装 Cygwin 并使用符号链接?)

  3. 在具有短名称的目录中创建所需的文件,将它们压缩/压缩,然后将它们解压到具有较长名称的目录中。或者“手动”创建 zip/tar 文件并将它们解压到所需位置。