Skip to main content

Is Unix find overwhelmed by multiple prunes and type f? [Resolved]

I have spent days on this Rubik's cube. Anything I do to fix one problem breaks another.

I am on POSIX compliant MacOS X 10.5 thru 10.14. I am calling this from a Perl script in a context of

  system ("find blah blah > FILENAME");

I need Unix 'find' to do all these things at once.

  • start at a volume root e.g. /Volumes/My HD
  • do not cross file systems
  • print files only, not directories or symlinks
  • do not even descend into multiple directories like net dev system. (I.e. do not explore /Volumes/foo/dev/ but do explore /Volumes/foo/Users/Jim/dev/github/twonky/)
  • the start point may contain spaces

Right now I am doing the following: (broken into several lines for readability; it's actually one long line)

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

The reason for the double / is find’s printout includes the // because the starting point ends in a /. The Prune paths must agree, or they won't match. Why does the starting point end in /? Because if it doesn't, find fails on any starting point with a space in the name, like "My HD". Tried that.

Right now, find is only excludes the first directory in the list. The rest, it just ignores. I am currently testing on OS X 10.5 but I need something that works everywhere.

Is multiple prunes + files only + spaces in filenames a bridge too far? Am I just asking too much of find?

Question Credit: Harper
Question Reference
Asked July 21, 2019
Tags: find
Posted Under: Unix Linux
3 Answers

You need an "or" to accomplish the second match -- no single path will match both -path '/Volumes/foo//dev/*' and -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

credit: L. Scott Johnson
Answered July 21, 2019

With your help, I was able to stabilize "find". However moving the code from OS X 10.5 to 10.10 broke it again. That was the last straw. 'find' is simply too obtuse, underdocumented and inconsistent, and it's a unix core feature for Pete's sake! This. This is why I hate other people's code. I started to hunker down to learn File::Find, then thought "what am I doing? I can code this myself in 20 minutes".

Which I summarily did.

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

And it's fast. And light - this code is half the size (and more maintainable) than the code that formatted "find"'s arg list!

credit: Harper
Answered July 21, 2019
Your Answer