Find Command – Add Arguments Between {} and +

argumentsfind

I want to use find to locate files, then copy those to a directory, so I tried:

find . -name '*.png' -exec cp {} /tmp/dest +

However, this fails with

find: missing argument to `-exec'

When I replace the + by a ; it works, but invokes cp for every file individually. How can I add a trailing argument (such as a destination directory) when using the + form of -exec?

Of course, in this case I can work around the apparent limitation by using cp -t (such as indicated in this post on Stack Overflow, but that solution is specific to cp. Instead, I might be using rsync, scp, or some other tool. Is there a general way to add arguments between {} and + in find -exec?

Best Answer

No, if you're using -exec ... {} +, there may be nothing between {} and + apart from whitespace. There is no way around that.

From the POSIX standard specification of the find command:

-exec utility_name [argument ...] ;
-exec utility_name [argument ...] {} +

The end of the primary expression shall be punctuated by a <semicolon> or by a <plus-sign>. 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.

A more generic solution would possibly be

find ... -exec sh -c 'cp "$@" /tmp/dest' sh {} +

Here, an inline sh -c script is executed with batches of arguments from find. Inside the inline script, "$@" will be the list of passed arguments (individually quoted), which allows us to place them as we want them on the cp command line.

This allows us to use non-GNU cp (on e.g. macOS or other BSD systems, where there is no -t option) or any other utility where one may want to add other arguments to the end of the list of pathnames coming from find.

Related:


Nobody asked for this, but anyway...

With the destination directory in a variable, destdir:

destdir=/tmp/dest

find ... -exec sh -c 'destdir=$1; shift; cp "$@" "$destdir"' sh "$destdir" {} +

Note that the destdir in the shell calling find is a separate variable to the destdir in the sh -c script.

Or, with bash:

destdir=/tmp/dest

find ... -exec bash -c 'cp "${@:2}" "$1"' bash "$destdir" {} +

This is "slicing" the "$@" list to rearrange it appropriately for the cp command line, without extracting $1, the destination directory pathname, into a separate variable.

Related Question