Как создать, а затем использовать длинные пути Windows из Perl?
У меня есть часть процесса сборки, которая создает ужасно длинные пути в 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 документирует это в именах файлов, путей и пространств имен. Их исправление предполагает добавление \\?\
перед любым путем для доступа к API имени файла Unicode. Однако, это, кажется, не полное исправление для сценария Perl, потому что он все еще терпит неудачу:
use File::Path qw( make_path );
make_path( '\\\\?\\C:\\.....' ); # still fails if path is over MAX_PATH, works otherwise
Это может быть потому, что make_path
разбирает свой аргумент, а затем проходит по каталогам по одному уровню за раз, так \\?\
относится только к верхнему уровню, который находится в пределах MAX_PATH
,
Я откопал отчет об ошибке в ActiveState, в котором говорится, что есть еще кое-что, что мне нужно исправить, чтобы добраться до имен файлов Unicode, и Ян Дюбуа дает немного больше подробностей в Re: "длинные" имена файлов в Windows 2K / XP, хотя я не уверен, что это относится (и является очень старым). Perlrun упоминает, что это использование, чтобы быть работой -C
переключатель, но, видимо, эта часть была заброшена. В очереди perl RT имеется более поздняя ошибка 60888: Win32: поддержка полного юникода в именах файлов (используйте системные вызовы).
Миягава отмечает некоторые проблемы с именами файлов в Юникоде и Win32API::File, не упоминая длинные пути. Тем не менее, запись в форуме Win32API::File CPAN указывает только на страх, который приводит к гневу, к ненависти и так далее. В посте Perlmonks приведен пример. Как определить в файле файл с именем Unicode (UTF16-LE) в Windows?, Кажется Win32::CreateDirectory
это ответ, и я попробую это в следующий раз, когда буду рядом с машиной Windows.
Затем, предположим, я могу создать длинный путь. Теперь я должен научить Module::Build и, возможно, другим вещам справляться с этим. Это может быть легко с обезьянами, если Win32::GetANSIPathName()
делает то, что говорит на жестяной банке.
5 ответов
Прогресс:
Работает следующий скрипт: Он записывает строку в файл в каталоге с длинным путем и может читать ту же строку. (Успешный запуск не приводит к выводу консоли). Я также сделал громоздкое усилие переопределить 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]\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;
}
С помощью Win32::GetANSIPathName()
со встроенным open
не работает: возвращенный путь слишком длинный.
Смотрите историю изменений для неудачных экспериментов.
Следующий код фактически создает довольно глубокую (более 260 символов) структуру каталогов. По крайней мере, на моей машине:
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\0")); #dirty way to produce UTF-16LE string
$res = $cd->Call($dirname, 0);
print "$res\n";
$dir .= '\\abcde';
} while ( $res );
Это действительно должен быть комментарий, но размещение кода в комментариях вряд ли полезно.
UNC-пути также не работают:
C: \> чистая доля perlbuild e:\home\src
#!/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";
Результат:
mkdir \\ Computer \ perlbuild \ 0123456789 \ 0123456789 \ 0123456789 \ 0123456789 \ 0123456 789 \ 0123456789 \ 0123456789 \ 0123456789 \ 0123456789 \ 0123456789 \ 0123456789 \ 0123456789 \ 0123456789 \ 0123456789 \ 0123456789 \ 0123456789 \ 0123456789 \ 0123456789 \ 0123456789 \ 01 23456789 \ 0123456789: Нет такого файла или каталога; Имя файла или расширение слишком длинный в C:\Temp\k.pl строка 15
Я понимаю, что это не решение вашей конкретной проблемы. Тем не менее, существует множество сценариев, в которых возможность сопоставления очень длинного пути с буквой диска позволит обойти проблему и, следовательно, будет полезна при работе с очень длинными именами путей без необходимости разбираться с большим количеством Специфичный для Windows код и документы.
Несмотря на все усилия, которые я приложил, чтобы выяснить, как это сделать, я собираюсь рекомендовать как-то использовать SUBST
, Win32:: FileOp обеспечивает Subst
а также Unsubst
, Затем можно сопоставить рабочий каталог верхнего уровня с неиспользуемой буквой диска (которую можно найти с помощью Substed
). Я бы начал проверять с Z
и работает в обратном направлении.
Или вы можете раскошелиться, вызвать subst
Утилита без параметров, чтобы получить список текущих замен, выберите тот, которого нет.
Ничто из этого не является полностью безопасным, поскольку замены могут измениться в процессе сборки.
У меня было три мысли, все они вроде хаков:
Начните с некоторых коротких имен каталогов (C:\data_directory\a\b\c\d\4\5\6\...), а затем переименуйте каталоги (конечно, начиная с самого глубокого каталога).
Создать ярлык Windows для умеренно длинного пути и создавать файлы и подкаталоги оттуда? (Или установить Cygwin и использовать символические ссылки?)
Создайте нужные файлы в каталоге с коротким именем, zip/tar их и распакуйте в каталог с более длинным именем. Или создайте zip / tar файлы "вручную" и распакуйте их в нужном месте.