在 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
问题的根源在于您尝试验证文件是否已创建的方法。欲了解更多信息,请阅读下面的详细说明。
事实如下:
perl
在使用 mkdirL
或 openL
调用时不会给出任何错误。
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 版本,例如CreateFileW、openL
不运行此类问题。
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}
我遇到无法在名称长度超过 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
问题的根源在于您尝试验证文件是否已创建的方法。欲了解更多信息,请阅读下面的详细说明。
事实如下:
perl
在使用mkdirL
或openL
调用时不会给出任何错误。perl
可以打开创建的文件,并读取内容,使用openL
.
因此,问题是由于无论您使用的是什么工具,要么使用 Windows API 调用的 ANSI 版本,要么指定相对路径,要么两者兼而有之,并且因此它们的路径限制为 260 个字符。
为了测试这一点,我 运行 D:\t
下的脚本。瞧,当 $i = 250
:
D:\t
是四个字,\test
又是五个字。因此,250 + 9 = 259,当您添加另一个 \
.
使用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 版本,例如CreateFileW、openL
不运行此类问题。
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}