online - ¿Cómo creo y luego uso largas rutas de Windows desde Perl?
tag rename (5)
Progreso:
El siguiente script funciona: escribe una cadena en un archivo en un directorio con una ruta larga y puede leer la misma cadena. (Una ejecución exitosa no produce salida de consola). También he hecho un esfuerzo incómodo para anular 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;
}
El uso de Win32::GetANSIPathName()
con la open
integrada no funciona: la ruta devuelta es demasiado larga.
Ver el historial de edición para experimentos fallidos.
Tengo parte de un proceso de compilación que crea rutas horriblemente largas en Windows. No es mi culpa. Es profundo en varios directorios, y ninguno de los nombres de directorios es anormalmente largo; son lo suficientemente largos y numerosos como para superar MAX_PATH
(260 caracteres). No estoy usando nada más que ASCII en estos nombres.
El gran problema es que la explosión ocurre en las entrañas de Module::Build durante el destino dist
, aunque creo que el sistema de compilación no importa porque harían los mismos directorios.
La creación de uno de estos directorios demasiado largos con File::Path
falla:
use File::Path qw( make_path );
make_path( ''C://.....'' ); # fails if path is over 260 chars
De manera similar, la construcción de cada nivel de directorio a mano falla una vez que la ruta absoluta pasaría por MAX_PATH
.
Esto no es nuevo, no es culpa de Perl y Microsoft lo documenta en Nombrar archivos, rutas y espacios de nombres . Su solución sugiere agregar el //?/
Delante de cualquier ruta para acceder a la API de nombre de archivo Unicode. Sin embargo, esa no parece ser la solución completa para un script de Perl porque aún falla:
use File::Path qw( make_path );
make_path( ''////?//C://.....'' ); # still fails if path is over MAX_PATH, works otherwise
Esto podría deberse a que make_path
extrae su argumento y luego pasa a través de los directorios nivel por nivel, por lo que //?/
sólo se aplica al nivel superior, que está dentro de MAX_PATH
.
Desenterré un informe de errores en ActiveState que sugiere que hay algo más que debo arreglar para llegar a los nombres de archivo de Unicode, y Jan Dubois da un poco más de detalles en Re: nombres de archivo "largos" en Windows 2K / XP , aunque estoy No estoy seguro de que se aplique (y es extremadamente viejo). perlrun menciona que esto perlrun ser el trabajo del conmutador -C
, pero aparentemente esa parte fue abandonada. La cola de RT de Perl tiene un error más reciente 60888: Win32: admite unicode completo en los nombres de archivo (use llamadas de todo el sistema) .
Miyagawa observa algunos problemas con el nombre de archivo Unicode y Win32API::File sin mencionar específicamente las rutas largas. Sin embargo, la entrada al foro de Win32API :: File CPAN parece indicar solo miedo, lo que conduce a la ira, lo que lleva al odio, y así sucesivamente. Hay un ejemplo en la publicación de Perlmonks ¿Cómo Perlmonks un archivo con un nombre de archivo Unicode (UTF16-LE) en Windows? . Parece que Win32::CreateDirectory
es la respuesta, y lo intentaré la próxima vez que esté al lado de una máquina con Windows.
Luego, suponiendo que puedo crear el camino largo. Ahora tengo que enseñar Module :: Build, y tal vez otras cosas, para manejarlo. Eso podría ser fácil con Monkeypatches si Win32::GetANSIPathName()
hace lo que dice en la lata.
El siguiente código en realidad crea una estructura de directorios bastante profunda (más de 260 caracteres). Al menos en mi máquina:
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 );
Esto realmente debería ser un comentario, pero el código de publicación en los comentarios no es útil.
Las rutas UNC tampoco funcionan:
C:/> net share 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";
Resultado:
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: No such file or directory; The filename or extension is too long at C:/Temp/k.pl line 15
Tuve tres pensamientos, todos ellos tipo de hacks:
Comience con algunos nombres de directorio cortos (C: / directorio_de_datos / a / b / c / d / 4 / 5 / 6 / ...) y luego cambie el nombre de los directorios (comenzando con el directorio más profundo primero, por supuesto).
¿Crear acceso directo de Windows a una ruta moderadamente larga y crear archivos y subdirectorios desde allí? (¿O instalar Cygwin y usar enlaces simbólicos?)
Cree los archivos deseados en un directorio con un nombre corto, ciérrelos / descomprímalos y descomprímalos en el directorio con el nombre más largo. O cree archivos zip / tar "a mano" y descomprímalos en la ubicación deseada.
Entiendo que esto no es una solución a su problema específico. Sin embargo, hay muchos escenarios en los que poder trazar un camino muy largo a una letra de unidad le permitiría evitar el problema y, por lo tanto, sería útil para tratar con nombres de camino muy largos sin tener que vadearlos. Código específico de Windows y documentos.
A pesar de todo el esfuerzo que puse para averiguar cómo hacer esto, voy a recomendar de alguna manera el uso de SUBST
. Win32::FileOp proporciona Unsubst
y Unsubst
. Luego puede asignar el directorio de trabajo de nivel superior a una letra de unidad no utilizada (que puede encontrar usando Substed
). Comenzaría a verificar con Z
y trabajar hacia atrás.
O bien, puede desembolsar, invocar subst
utilidades sin parámetros para obtener una lista de las sustituciones actuales, elija una que no esté allí.
Nada de esto es completamente seguro ya que las sustituciones podrían cambiar durante el proceso de construcción.