
He pasado días en este cubo de Rubik. Cualquier cosa que haga para solucionar un problema arruina otro.
Estoy en MacOS X 10.5 a 10.14 compatible con POSIX. Estoy llamando a esto desde un script Perl en un contexto de
system ("find blah blah > FILENAME");
Necesito "buscar" de Unix para hacer todas estas cosas a la vez.
- comenzar en la raíz de un volumen, por ejemplo
/Volumes/My HD
- no cruzar sistemas de archivos
- imprimir solo archivos, no directorios ni enlaces simbólicos
- ni siquiera desciendasmúltipledirectorios como
net dev system
. (Es decir, no explore /Volumes/foo/dev/ pero sí explore /Volumes/foo/Users/Jim/desarrollador/github/twonky/) - el punto de inicio puede contener espacios
En este momento estoy haciendo lo siguiente: (dividido en varias líneas para facilitar la lectura; en realidad es una línea larga)
Find -x '/Volumes/foo/'
-path '/Volumes/foo//dev/*' -prune
-path '/Volumes/foo//net/*' -prune
-path '/Volumes/foo//system/*' -prune
-o -type f -print
La razón del doble / esencontrarLa impresión incluye // porque el punto inicial termina en /. Las rutas de Prune deben coincidir o no coincidirán. ¿Por qué el punto de partida termina en /? Porque si no es así,encontrarfalla en cualquier punto de partida con un espacio en el nombre, como "Mi HD". Lo intenté.
En este momento, buscar solo excluye el primer directorio de la lista. El resto simplemente lo ignora. Actualmente estoy realizando pruebas en OS X 10.5 pero necesito algo que funcione en todas partes.
¿Múltiples podas + solo archivos + espacios en los nombres de archivos son un puente demasiado lejos? ¿Estoy pidiendo demasiado?encontrar?
Respuesta1
Necesita una "o" para lograr la segunda coincidencia: ninguna ruta coincidirá con ambas -path '/Volumes/foo//dev/*'
y-path '/Volumes/foo//net/*'
Find -x '/Volumes/foo/'
\( -path '/Volumes/foo//dev/*'
-o -path '/Volumes/foo//net/*'
-o -path '/Volumes/foo//system/*' \) -prune
-o -type f -print
Respuesta2
Mi respuesta con una solución Perl pura.
Con esta caja de arena:
$ tree -F Volumes/
Volumes/
└── My\ HD/
├── Users/
│ └── Jim/
│ └── dev/
│ └── github/
│ └── twonky/
│ └── i_there.txt
├── dev/
├── net/
├── start.bat
└── system/
└── hello
9 directories, 3 files
El siguiente código Perl usando File::Find
:
#!/usr/bin/perl
use strict;
use warnings;
use feature 'say';
use File::Find;
my $start = 'Volumes/My HD';
my $start_dev = (stat($start))[0];
my @exclude = qw/net dev system/;
my %skipdir;
sub wanted {
my $name = $_;
return if (stat($name))[0] != $start_dev;
$skipdir{$File::Find::name} = 1 if $File::Find::dir eq $start && grep { $name eq $_ } @exclude;
if (exists($skipdir{$File::Find::dir})) {
$skipdir{$File::Find::name} = 1 if -d $name;
return;
}
return if ! -f $name;
say "Got: $File::Find::name";
}
my %args = (
wanted => \&wanted,
follow => 1,
follow_skip => 1,
);
find(\%args, $start);
da lo esperado (si te entendí bien):
Got: Volumes/My HD/start.bat
Got: Volumes/My HD/Users/Jim/dev/github/twonky/i_there.txt
Es un POC, se puede mejorar.
Tenga en cuenta también que tiene la find2perl
herramienta documentada para poder convertir una find
invocación específica al código Perl asociado utilizando File::Find
el mismo criterio.
Ahora Path::Class
el código puede parecer más simple/fácil de leer (para el mismo resultado):
#!/usr/bin/perl
use strict;
use warnings;
use feature 'say';
use Path::Class;
my $start = Path::Class::Dir->new('Volumes/My HD');
my @exclude = qw/net dev system/;
$start->recurse(callback => sub {
my $node = shift;
if ($node->is_dir) {
return $node->PRUNE if $node->parent eq $start && grep { $node->dir_list(-1) eq $_ } @exclude;
return;
}
return $node->PRUNE if $node->stat()->dev != $start->stat()->dev;
say 'Got: ', $node->stringify();
}, preorder => 1)
Respuesta3
Con tu ayuda, pude estabilizar "buscar". Sin embargo, mover el código de OS X 10.5 a 10.10lo rompió de nuevo. Eso fue el colmo. 'buscar' es simplemente demasiado obtuso, poco documentado e inconsistente, ¡y es una característica central de Unix, por el amor de Dios! Este. Por eso odio el código de otras personas. Empecé a agacharme para aprender File::Find, luego pensé "¿qué soy?"¿haciendo? Puedo codificar esto yo mismoen 20 minutos".
Lo cual hice sumariamente.
sub iterate {
my ($mydir, $ref_FH, $homevol, $ref_excludes) = @_; # last is ref to hash
return if (defined ($ref_excludes -> {$mydir})); # No excludes
my $thisvol = (stat($mydir))[0]; # What's my volume?
return if ($thisvol != $homevol) ; # No crossing volumes
opendir (my $DIR, $mydir);
while (defined (my $file = readdir($DIR))) {
next if ($file eq '.' or $file eq '..');
my $full = "$mydir/$file";
if (-l $full) { # symlink
# nope
} elsif (-f $full) { # file
print {$$ref_FH} "$full\n"; # print it
} elsif (-d $full) { # dir
&iterate($full, $ref_FH, $homevol, $ref_excludes); # iterate
}
}
}
Y es rápido. Y ligero: ¡este código tiene la mitad del tamaño (y es más fácil de mantener) que el código que formateó la lista de argumentos de "buscar"!