
Passei dias neste cubo de Rubik. Qualquer coisa que eu faça para resolver um problema quebra outro.
Estou no MacOS X 10.5 a 10.14 compatível com POSIX. Estou chamando isso de um script Perl em um contexto de
system ("find blah blah > FILENAME");
Eu preciso que o Unix 'encontre' para fazer todas essas coisas de uma vez.
- comece em uma raiz de volume, por exemplo
/Volumes/My HD
- não cruze sistemas de arquivos
- imprimir apenas arquivos, não diretórios ou links simbólicos
- nem sequer desçamúltiplodiretórios como
net dev system
. (Ou seja, não explore /Volumes/foo/dev/ mas explore /Volumes/foo/Users/Jim/desenvolvedor/github/twonky/) - o ponto inicial pode conter espaços
No momento estou fazendo o seguinte: (dividido em várias linhas para facilitar a leitura; na verdade, é uma linha longa)
Find -x '/Volumes/foo/'
-path '/Volumes/foo//dev/*' -prune
-path '/Volumes/foo//net/*' -prune
-path '/Volumes/foo//system/*' -prune
-o -type f -print
A razão para o duplo / éencontrarA impressão de inclui // porque o ponto inicial termina em /. Os caminhos do Prune devem concordar ou não corresponderão. Por que o ponto de partida termina em /? Porque se isso não acontecer,encontrarfalha em qualquer ponto inicial com um espaço no nome, como "Meu HD". Tentei isso.
No momento, find exclui apenas o primeiro diretório da lista. O resto, simplesmente ignora. Atualmente estou testando no OS X 10.5, mas preciso de algo que funcione em qualquer lugar.
Várias ameixas + apenas arquivos + espaços em nomes de arquivos são uma ponte longe demais? Estou apenas pedindo demaisencontrar?
Responder1
Você precisa de um "ou" para realizar a segunda correspondência - nenhum caminho corresponderá a ambos -path '/Volumes/foo//dev/*'
e-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
Responder2
Minha resposta com uma solução Perl pura.
Com esta caixa de areia:
$ tree -F Volumes/
Volumes/
└── My\ HD/
├── Users/
│ └── Jim/
│ └── dev/
│ └── github/
│ └── twonky/
│ └── i_there.txt
├── dev/
├── net/
├── start.bat
└── system/
└── hello
9 directories, 3 files
O seguinte 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);
dá o esperado (se bem entendi):
Got: Volumes/My HD/start.bat
Got: Volumes/My HD/Users/Jim/dev/github/twonky/i_there.txt
É um POC, pode ser aprimorado.
Observe também que você possui a find2perl
ferramenta documentada para poder converter uma find
invocação específica para o código Perl associado usando File::Find
os mesmos critérios.
Agora com Path::Class
o código pode parecer mais simples/fácil de ler (para o mesmo 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)
Responder3
Com a sua ajuda, consegui estabilizar o "encontrar". No entanto, mover o código do OS X 10.5 para 10.10quebrou de novo. Essa foi a gota d’água. 'find' é simplesmente muito obtuso, pouco documentado e inconsistente, e é um recurso central do Unix, pelo amor de Deus! Esse. É por isso que odeio o código de outras pessoas. Comecei a me agachar para aprender File::Find, então pensei "o que eu soufazendo? Eu posso codificar isso sozinhoem 20 minutos".
O que eu fiz 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
}
}
}
E é rápido. E leve - esse código tem metade do tamanho (e é mais fácil de manter) do que o código que formatou a lista de argumentos de "find"!