Search code examples
shellfindportabilityxargsbsd

How to use xargs with BSD find?


With GNU find, it is easy to pipe to xargs. A typical (useless) example:

find /var/log -name "*.log" | xargs dirname

This returns all the directory names containing some log file.

The same command with BSD find does not work, ending with:

usage: dirname path

That is xargs is unable to pass file list entries to dirname.

BSD find's manpage mentions the -exec and -execdir options, stating "This behaviour is similar to that of xargs(1)."

-exec utility [argument ...] {} + Same as -exec, except that ``{}'' is replaced with as many pathnames as possible for each invocation of utility. This behaviour is similar to that of xargs(1).

-execdir utility [argument ...] {} + Same as -execdir, except that ``{}'' is replaced with as many pathnames as possible for each invocation of utility. This behaviour is similar to that of xargs(1).

Each time I fall back on these two flags, I have to read the documentation again. I seem unable to remember their usage! Also, I am concerned with script portability across GNU/BSD systems, basically Linux, Open/FreeBSD, and MacOS.

Any way to pipe BSD find to xargs, or -exec is really the only option?


Solution

  • Both GNU and FreeBSD version of xargs support a way to pass the strings from stdin to the command as part of the -I flag. All you need to is

    find /var/log -name "*.log" | xargs -I {} dirname -- "{}"
    

    The GNU xargs page says about the flag as

    -I replace-str Replace occurrences of replace-str in the initial-arguments with names read from standard input.

    This provides an alternate way than using -exec or -execdir. However, having said, that using -exec is not too complex for your case.

    find /var/log -name "*.log" -type f -exec dirname "{}" \;