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

时间:2015-02-06 10:33:35

标签: windows perl ntfs

我遇到了一个问题,我无法在名称长度超过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;
}

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

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

更新

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

更新2:

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

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

1 个答案:

答案 0 :(得分:4)

TL; DR

问题的根源是您尝试验证文件是否已创建的方法。有关更多信息,请阅读以下详细说明。

以下是事实:

    使用perlmkdirL来电时,
  1. openL不会出现任何错误。
  2. perl可以使用openL打开创建的文件并阅读内容。
  3. 因此问题是由于您使用的任何工具使用ANSI版本的Windows API调用,或指定相对路径或两者的组合,因此它们的路径限制为260个字符。 / p>

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

    时无法打开文件

    With run under D:\t, GVim fails to open file at $i=250

    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);
    

    你会得到:

    Notepad can open when given short path

    因此,请提供您不能依赖的任何外部程序的短路径来使用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 window showing the directories created

    GVim Window showing one of the files

    现在,我可以报告资源管理器在C:\...\Temp\test\0220aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

    之后导航到任何目录时遇到问题

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

    GVim having trouble with 221 character directory name

    据推测,GVim会从Naming Files, Paths, and Namespaces

    进入以下版本
      

    由于您不能将"\\?\"前缀与相对路径一起使用,因此相对路径始终限制为总共MAX_PATH个字符。

    巧合的是,%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内部规范化路径,所以它们遵循“指定扩展长度路径,使用"\\?\"前缀”建议,然后使用Unicode版本API调用,例如CreateFileWopenL不会遇到此类问题。

      

    在此函数的ANSI版本中,名称仅限于MAX_PATH个字符。要将此限制扩展为32,767个宽字符,请调用函数的Unicode版本并将"\\?\"添加到路径中。有关详细信息,请参阅Naming Files, Paths, and Namespaces

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

    另请参阅“Why is Perl system call failing to invoke internal Windows command?”了解qx{cmd /c rd /s /q test}

    的说明