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 清單的程式碼的一半(並且更易於維護)!

相關內容