Ist Unix find durch mehrere Prunes und den Typ f überfordert?

Ist Unix find durch mehrere Prunes und den Typ f überfordert?

Ich habe Tage mit diesem Zauberwürfel verbracht. Alles, was ich tue, um ein Problem zu lösen, führt zu einem anderen.

Ich verwende POSIX-kompatibles MacOS X 10.5 bis 10.14. Ich rufe dies aus einem Perl-Skript im Kontext von

  system ("find blah blah > FILENAME");

Ich brauche Unix „find“, um all diese Dinge gleichzeitig zu tun.

  • Beginnen Sie bei einer Lautstärkenwurzel, z. B./Volumes/My HD
  • nicht über Dateisysteme hinweg
  • nur Dateien drucken, keine Verzeichnisse oder symbolische Links
  • nicht einmal hinabsteigen inmehrereVerzeichnisse wie net dev system. (D. h., erkunden Sie nicht /Volumes/foo/dev/, sondern /Volumes/foo/Users/Jim/Entwickler/github/twonky/)
  • Der Startpunkt kann Leerzeichen enthalten

Im Moment mache ich Folgendes: (zur besseren Lesbarkeit in mehrere Zeilen aufgeteilt, eigentlich ist es eine lange Zeile)

 Find -x '/Volumes/foo/' 
    -path '/Volumes/foo//dev/*' -prune
    -path '/Volumes/foo//net/*' -prune
    -path '/Volumes/foo//system/*' -prune
    -o -type f -print

Der Grund für das doppelte / istfindenDer Ausdruck von enthält das //, weil der Startpunkt mit einem / endet. Die Prune-Pfade müssen übereinstimmen, sonst stimmen sie nicht überein. Warum endet der Startpunkt mit /?? Denn wenn nicht,findenschlägt bei jedem Startpunkt mit einem Leerzeichen im Namen fehl, wie etwa „Meine Festplatte“. Das habe ich versucht.

Im Moment schließt find nur das erste Verzeichnis in der Liste aus. Der Rest wird einfach ignoriert. Ich teste derzeit unter OS X 10.5, aber ich brauche etwas, das überall funktioniert.

Sind mehrere Beschneidungen + nur Dateien + Leerzeichen in Dateinamen zu viel verlangt? Verlange ich einfach zu viel vonfinden?

Antwort1

Sie benötigen ein „oder“, um die zweite Übereinstimmung zu erreichen – kein einzelner Pfad wird sowohl als auch als -path '/Volumes/foo//dev/*'übereinstimmen-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

Antwort2

Meine Antwort mit einer reinen Perl-Lösung.

Mit dieser Sandbox:

$ tree -F Volumes/ 
Volumes/ 
└── My\ HD/
    ├── Users/
    │   └── Jim/
    │       └── dev/
    │           └── github/
    │               └── twonky/
    │                   └── i_there.txt
    ├── dev/
    ├── net/
    ├── start.bat
    └── system/
        └── hello

9 directories, 3 files

Der folgende Perl-Code verwendet 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);

gibt das erwartete (wenn ich Sie richtig verstanden habe):

Got: Volumes/My HD/start.bat
Got: Volumes/My HD/Users/Jim/dev/github/twonky/i_there.txt

Es handelt sich um einen POC, er kann erweitert werden.

Beachten Sie auch, dass Sie über das find2perldokumentierte Tool verfügen, um einen bestimmten Aufruf unter Verwendung derselben Kriterien findin den zugehörigen Perl-Code konvertieren zu können.File::Find

Nun Path::Classscheint der Code einfacher/leichter lesbar zu sein (bei gleichem Ergebnis):

#!/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)

Antwort3

Mit Ihrer Hilfe konnte ich "find" stabilisieren. Allerdings wurde der Code von OS X 10.5 auf 10.10 verschoben.hat es wieder kaputt gemacht. Das war der Tropfen, der das Fass zum Überlaufen brachte. 'find' ist einfach zu stumpfsinnig, unzureichend dokumentiert und inkonsistent, und es ist um Himmels Willen ein Unix-Kernfeature! Das. Das ist der Grund, warum ich den Code anderer Leute hasse. Ich fing an, mich mit File::Find zu beschäftigen, dachte dann aber: "Was mache ichmache ich? Ich kann das selbst codierenin 20 Minuten".

Was ich kurzerhand tat.

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
    }
  }
}

Und es ist schnell. Und leicht – dieser Code ist halb so groß (und wartungsfreundlicher) als der Code, der die Argumentliste von „find“ formatiert hat!

verwandte Informationen