在 Windows 上使用 Perl 在名称长度超过 220 个字符的目录中创建文件

Creating a file using Perl on Windows in a directory the length of whose name exceeds 220 characters

我遇到无法在名称长度超过 220 个字符的目录下创建文件的问题。

以下是重现观察到的行为的测试脚本,至少在我的机器上是这样:

use warnings;
use strict;

use Win32::LongPath;

print system ('rmdir /s /q test');
mkdirL('test');

for my $i (200 .. 255) {

  my $dir_name = 'test/' . sprintf("%04d", $i) . ('a' x ($i-4));
  mkdirL($dir_name);

  openL(\my $fh, '>', "$dir_name/" . ('_' x 200) . '.txt') or die "$^E";
  print $fh 'Hello!';
# closeL $fh;
}

此脚本将在 0220aaaa...aaa/ 下创建一个 _________.....___.txt 文件,但不会在 0221aaaa...aaa/.

下创建

这个问题是否有原因?如何更改脚本以便在所有目录中创建 *.txt 文件?

更新:

脚本在 运行 时不会终止或产生任何错误消息。

更新 2:

这个问题 没有 已经在 Why does the 260 character path length limit exist in Windows? 中有答案正如另一个问题及其答案清楚地表明,这个路径长度限制是 Windows 问题,而不是 NTFS 问题。 NTFS 允许长度最多为 32K 个字符的路径。

事实上,我的测试脚本能够创建最多 255 个字符的目录(正如 NTFS 规范所预期的那样),当目录名称超过 220 个字符时,似乎无法在目录中存储文件。

TL;DR

问题的根源在于您尝试验证文件是否已创建的方法。欲了解更多信息,请阅读下面的详细说明。

事实如下:

  1. perl 在使用 mkdirLopenL 调用时不会给出任何错误。
  2. perl 可以打开创建的文件,并读取内容,使用 openL.

因此,问题是由于无论您使用的是什么工具,要么使用 Windows API 调用的 ANSI 版本,要么指定相对路径,要么两者兼而有之,并且因此它们的路径限制为 260 个字符。

为了测试这一点,我 运行 D:\t 下的脚本。瞧,当 $i = 250:

时,GVim 无法打开文件

D:\t是四个字,\test又是五个字。因此,250 + 9 = 259,当您添加另一个 \.

时,它会达到 260

使用shortpathL

试试这个:

#!/usr/bin/env perl

use strict;
use warnings;

use Win32::LongPath;

`cmd /c rd /s /q test`;

mkdirL('test') or die "$^E";

my $dir_length = 255;
my $dir_name = 'test/'
             . sprintf("%04d", $dir_length)
             . ('a' x ($dir_length - 4))
;

mkdirL($dir_name) or die "$^E";

my $file_name = "$dir_name/" . ('z' x 200) . '.txt';

printf "% 3d\n", length $file_name;

openL(\my $fh, '>', $file_name) or die "$^E";

print $fh "Hello!\n" or die "$^E";

close $fh or die "$^E";

system 'notepad.exe', shortpathL($file_name);

您将获得:

因此,请提供您不能依赖使用 Unicode 接口的任何外部程序的短路径。

冗长的解释

现在我有机会在 64-bit Windows 8.1 system 上实际尝试这个,我无法重现这个问题。

这是我使用的代码:

#!/usr/bin/env perl

use strict;
use warnings;

use Win32::LongPath;

`cmd /c rd /s /q test`;

mkdirL('test')
    or die "$^E";

for my $i (200 .. 255) {
    my $dir_name = 'test/' . sprintf("%04d", $i) . ('a' x ($i-4));
    mkdirL($dir_name) or die "$^E";

    my $file_name = "$dir_name/" . ('_' x 200) . '.txt';

    printf "% 3d\n", length $file_name;

    openL(\my $fh, '>', $file_name)
        or die "$^E";

    print $fh 'Hello!' or die "$^E";

    close $fh or die "$^E";
}

这是输出:

C:\…\Temp> perl tt.pl          
 410                                                   
 411                                                   
 412                                                   
 413                                                   
 414                                                   
 415                                                   
…
 460    
 461    
 462    
 463    
 464    
 465

我还附上了几张截图:

现在,我可以报告 Explorer 在 C:\...\Temp\test20aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

之后无法导航到任何目录

而且 GVim 无法打开后续目录中的文件。也就是说,从脚本中发出的 system 'c:/.../gvim.exe', $file_name; 结果是

大概GVim 运行s从Naming Files, Paths, and Namespaces

变成了下面的

Because you cannot use the "\?\" prefix with a relative path, relative paths are always limited to a total of MAX_PATH characters.

巧合的是,我的%TEMP%目录的路径长度恰好是33个字符。加上 5(\test 的长度),我们得到 38。加上 221,我们得到 259。现在,当您向该字符串添加目录分隔符时,您会点击 260.

这让我想到,您正在创建的工作目录的完整路径的长度是多少test

好消息是,perl 能够读回写入的所有内容:

#!/usr/bin/env perl

use strict;
use warnings;

use Win32::LongPath;

`cmd /c rd /s /q test`;

mkdirL('test')
    or die "$^E";

for my $i (220 .. 255) {
    my $dir_name = 'test/' . sprintf("%04d", $i) . ('a' x ($i-4));
    mkdirL($dir_name) or die "$^E";

    my $file_name = "$dir_name/" . ('_' x 200) . '.txt';

    printf "% 3d\n", length $file_name;

    openL(\my $fh, '>', $file_name)
        or die "$^E";

    print $fh 'Hello!' or die "$^E";

    close $fh or die "$^E";

    openL(\my $in, '<', $file_name)
        or die "$^E";

    print <$in>, "\n" or die "$^E";

    close $in or die "$^E";
}

输出:

…
 459  
Hello!
 460  
Hello!
 461  
Hello!
 462  
Hello!
 463  
Hello!
 464  
Hello!
 465  
Hello!

因为 Win32::LongPath 在内部对路径进行规范化,因此它们遵循“要指定扩展长度路径,请使用 "\?\" 前缀”建议,然后使用 API 调用的 Unicode 版本,例如CreateFileWopenL不运行此类问题。

In the ANSI version of this function, the name is limited to MAX_PATH characters. To extend this limit to 32,767 wide characters, call the Unicode version of the function and prepend "\?\" to the path. For more information, see Naming Files, Paths, and Namespaces.

您如何验证文件是否已正确创建?

另见“Why is Perl system call failing to invoke internal Windows command?" for the explanation of qx{cmd /c rd /s /q test}