
Я потратил дни на этот кубик Рубика. Все, что я делаю для решения одной проблемы, ломает другую.
Я работаю на POSIX-совместимой MacOS X 10.5 - 10.14. Я вызываю это из скрипта Perl в контексте
system ("find blah blah > FILENAME");
Мне нужна команда Unix «find», чтобы выполнять все эти действия одновременно.
- начать с корня тома, например
/Volumes/My HD
- не пересекайте файловые системы
- печатать только файлы, а не каталоги или символические ссылки
- даже не спускайтесь внесколькокаталоги типа
net dev system
. (Т.е. не исследуйте /Volumes/foo/dev/, а исследуйте /Volumes/foo/Users/Jim/дев/github/twonky/) - начальная точка может содержать пробелы
Прямо сейчас я делаю следующее: (разбито на несколько строк для удобства чтения; на самом деле это одна длинная строка)
Find -x '/Volumes/foo/'
-path '/Volumes/foo//dev/*' -prune
-path '/Volumes/foo//net/*' -prune
-path '/Volumes/foo//system/*' -prune
-o -type f -print
Причина двойного / заключается в том,находитьРаспечатка включает //, потому что начальная точка заканчивается на /. Пути Prune должны совпадать, иначе они не будут совпадать. Почему начальная точка заканчивается на /? Потому что если это не так,находитьне работает на любой начальной точке с пробелом в имени, например "Мой HD". Пробовал.
Сейчас find исключает только первый каталог в списке. Остальное он просто игнорирует. Сейчас я тестирую на OS X 10.5, но мне нужно что-то, что работает везде.
Многократные сокращения + только файлы + пробелы в именах файлов — это слишком большой мост? Я просто слишком многого прошунаходить?
решение1
Вам нужно «или», чтобы выполнить второе сопоставление — ни один путь не будет соответствовать обоим -path '/Volumes/foo//dev/*'
и-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
решение2
Мой ответ — решение на чистом Perl.
С этой песочницей:
$ tree -F Volumes/
Volumes/
└── My\ HD/
├── Users/
│ └── Jim/
│ └── dev/
│ └── github/
│ └── twonky/
│ └── i_there.txt
├── dev/
├── net/
├── start.bat
└── system/
└── hello
9 directories, 3 files
Следующий код Perl с использованием 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);
дает ожидаемое (если я правильно понял):
Got: Volumes/My HD/start.bat
Got: Volumes/My HD/Users/Jim/dev/github/twonky/i_there.txt
Это POC, его можно улучшить.
Обратите внимание также, что у вас есть find2perl
документированный инструмент, позволяющий преобразовать определенный find
вызов в связанный код Perl, используя File::Find
те же критерии.
Теперь Path::Class
код может показаться более простым/легким для чтения (при том же результате):
#!/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)
решение3
С вашей помощью мне удалось стабилизировать "find". Однако перенос кода с OS X 10.5 на 10.10сломал его снова. Это была последняя капля. 'find' просто слишком тупой, недостаточно документированный и непоследовательный, и это основная функция unix, ради всего святого! Это. Вот почему я ненавижу чужой код. Я начал затаиться, чтобы изучить File::Find, а потом подумал: "Что яделаю? Я могу сам это закодироватьчерез 20 мин".
Что я вкратце и сделал.
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
}
}
}
И он быстрый. И легкий — этот код вдвое меньше (и более удобен в обслуживании), чем код, который форматировал список аргументов «find»!