1

I'm on OS X and have defined a function called finderpackage that tries to determine if a folder is in fact a "package" (also, somewhat erroneously, known as a "bundle") according to the Finder.

My intended use for this was to limit find from descending into such folders which is sometimes useful.

One application would be finding the size of files in a subtree while treating apps, frameworks, document formats that are really folders, and similar as single "files", just like Finder would:

find . -xdev \( -exec finderpackage {} \; -exec du -sk {} \; -prune \) -or \( -type f -exec du -k {} \; \)

However, this example does not work as find complains: find: finderpackage: No such file or directory

Can I not use a zsh function in this way?

(Of course I can rewrite it as a script, I'm just curious if I have missed something about how functions are used?)

3

functions are features of the shell language. /bin/zsh is a command that is an interpreter of the zsh language. find is another command which is intended to find files. With -exec, it can execute a command. A zsh function is not a command.

find would need to have zsh interpret the code in the function. For that it would need to invoke zsh in a way that tells it to load the code of that function and run it with the file it has found. It can be done, like with:

find ... -exec zsh -c "(){$functions[finderpackage];}"' "$@"' zsh {} \; ...

(above, we're invoking zsh with an inline script (with -c) whose content uses an anonymous function ((){code} args) where the code is copied from that of the finderpackage function in the shell that invokes that find command).

But that means invoking one zsh command per file which is going to be terribly inefficient.

zsh globbing has recursive capabilities and qualifiers that make it almost equivalent to find but unfortunately, one thing it is missing is the ability to control how the directory traversal is done in an arbitrary fashion.

You can prune directories based on their name, like:

setopt extendedglob
print -rl -- (^*.pkg/)#*(.)

To prune the *.pkg directories. But you couldn't prune directories that are old or contain this or that file for instance. For that you'd have to resort to doing the directory traversal by hand.

One thing you could do, though that's not going to be very efficient either is to interact with find as a coproc and use its -ok:

coproc stdbuf -o0 find . -type f -exec printf 'f:%s\0' {} \; -o \
  -type d -exec printf 'd:%s\0' {} \; -ok true \; -prune 2> /dev/null
while IFS=: read -rpd '' type file; do
  case $type in
    (d)
      if finderpackage $file; then
        print -p y
        du -sk $file
      else
        print -p n
      fi;;
    (f)
      du -k $file
  esac
done
  • Ok, thanks. I was just thinking that, since find is executing in zsh, it should be able to get the definition (or the shell replacing it with a temporary script or something). And you are right, spawning a new shell for each file is ridiculously expensive. As I said, I'll just rewrite it as a script instead. Your last suggestion does not work, by the way, most "packages" (which has nothing to do with software distribution in OS X) are defined via metadata so (apart from a handful of known extensions) you need to query mdlsto know if it is a package. – PaulRein May 21 '14 at 11:23
  • BTW: the latest version of bosh can call a shell function from find. This is because find is a builtin command in bosh implemented via libfind and libfind now supports a new primary called -call that allows to call the shell without an execl() but by directly calling the shell interpreter. This is 40x faster than using -exec as long as only shell internals are used in that callback. – schily Aug 21 '18 at 21:42

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.