![Unix find 是否被多个 prune 和 type f 淹没了?](https://linux22.com/image/155116/Unix%20find%20%E6%98%AF%E5%90%A6%E8%A2%AB%E5%A4%9A%E4%B8%AA%20prune%20%E5%92%8C%20type%20f%20%E6%B7%B9%E6%B2%A1%E4%BA%86%EF%BC%9F.png)
我在这个魔方上花了好几天的时间。我为解决一个问题所做的任何事情都会破坏另一个问题。
我使用的是 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
双 / 的原因是寻找的打印输出包括//,因为起点以/结尾。修剪路径必须一致,否则它们将不匹配。为什么起点以/结尾?因为如果不这样做,寻找在名称中包含空格的任何起点上都会失败,例如“My HD”。试过了。
目前,查找仅排除列表中的第一个目录。其余的,它只是忽略。我目前正在 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
在你的帮助下,我能够稳定“查找”。然而,将代码从 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”的 arg 列表的代码的一半(并且更易于维护)!