Unix find 是否被多个 prune 和 type f 淹没了?

Unix find 是否被多个 prune 和 type f 淹没了?

我在这个魔方上花了好几天的时间。我为解决一个问题所做的任何事情都会破坏另一个问题。

我使用的是 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 列表的代码的一半(并且更易于维护)!

相关内容