olibre olibre - 11 months ago 36
Bash Question

Why find complains when executing another find command with option +

I have read Is it possible to pass a find command to -exec of another find command? but I do not understand why.

I know the example command in this question is not very useful, this is just an example command.

Try to run the following command:

$ find -type d -exec find {} -ls + -exec echo {} +
find: Only one instance of {} is supported with -exec ... +

From my point of view the printed error is not correct because my command is composed of two
each one having its own final

find -type d -exec find {} -ls + -exec echo {} +
<-----------------> <------------->
First command Second command

When trying the first command only, I got a different error:

$ find -type d -exec find {} -ls +
find: missing argument to `-exec'
Try 'find --help' for more information.

Of course, a workaround is to replace
. But I wonder why we got these error messages in the two above examples ??? Please, also provide elegant solutions :-)

For information, I am using
on Ubuntu 16.10 and

$ find --version
find (GNU findutils) 4.7.0-git
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Written by Eric B. Decker, James Youngman, and Kevin Dalley.
Features enabled: D_TYPE O_NOFOLLOW(enabled) LEAF_OPTIMISATION FTS(FTS_CWDFD) CBO(level=2)

Answer Source

When -exec is used with terminator + (in contrast with \;):

  • a given -exec action only support a single instance of placeholder {} and
  • that one and only instance must be placed last, just before the +.

Therefore, because the find command you're passing to -exec ... + invariably requires arguments after the {}, it cannot work.

In other words: Using -exec ... +, you cannot call any command that requires you to pass arguments after the list of filenames represented by {}.

As you imply in your question, these restrictions do NOT apply to use of -exec ... \;:
Using terminator \; causes find to invoke the command once per matching file, so that {} expands to a single filename, in which case you're free to place {}, possibly multiple times, anywhere in the command.
By contrast, -exec ... +, causes find to pass (typically) all matching filenames at once ({} expands to a filename list) to a (typically) single overall invocation of the command.
Depending on the specific command, using \; in lieu of + may be a viable workaround, but it has important performance implications: with large input sets, the difference between creating an external process once per filename and a single process overall will be very noticeable.

As for the error messages:

$ find -type d -exec find {} -ls + -exec echo {} +
find: Only one instance of {} is supported with -exec ... +

What you meant to be two -exec actions are parsed as one, because find keeps parsing until it finds {} immediately followed by +. Per the POSIX specification for find:Thanks, chepner.

Only a <plus-sign> that immediately follows an argument containing only the two characters "{}" shall punctuate the end of the primary expression. Other uses of the <plus-sign> shall not be treated as special.

When interpreted as a single action, it now contains 2 instances of {}, which isn't supported with +, hence the error message (which, overall, is confusing).

$ find -type d -exec find {} -ls +
find: missing argument to `-exec'

This error stems from {} not being the last argument before terminator +, which means that the + is not recognized as the terminator of the action, so the action as a whole is not syntactically valid.

Unfortunately, the error message confusingly suggests that the argument (the command to execute) is missing - perhaps a better wording would be to say the the argument is unterminated.