The syntax is:
find ... -exec cmd {} +
find
will find a number of files based on the criteria in ...
and run cmd
with that list of file paths as arguments, as many as possible without surpassing the limit on the size of the arguments to a command.
If needed it may split the list of files and call cmd
several times. For instance, it may end up calling:
cmd ./file1 ./file2 ... ./file3000
cmd ./file3001 ./file3002 ... ./file4321
A limitation with that is that {}
has to be last. You can't for instance write:
find ... -exec cmd {} other args +
like you could with ';'
instead of '+'
.
You can write:
find ... -exec echo foo {} +
but not:
find ... -exec echo {} foo +
So, if you do need to add some extra arguments to cmd
after the list of files, you have to resort to calling a shell. (Other reasons why you'd need to call a shell would be any time you need to use a shell feature like redirections, pipes, some string expansions....)
In sh -c 'inline-script' x a b c
, for the inline-script
, $0
is x
, $1
is a
, $2
is b
... so "$@"
is the list of those 3 arguments: a, b and c. So in:
find ... -exec sh -c 'cmd "$@" other arg' find-sh {} +
For the inline script, $0
(which is used for instance when displaying error messages) is set to find-sh
and "$@"
is the list of files (what find
expands {}
to).
By using the exec
special builtin of the shell:
find ... -exec sh -c 'exec cmd "$@" other arg' find-sh {} +
We tell the shell not to fork an extra process to run cmd
, but instead to run it in the same process (replacing the running shell process with that command). Some shells like bash
, zsh
and some implementations of ksh
do that implicitly for the last command in a script.
In this case, you can work around find
's exec grammar by capturing a brace expression and using a back reference in the replacement text:
$ cat f1 f2
f1: hello{}a
f2: hello{}a
$ find . -type f -exec sed -i 's/hello\([{][}]\)a/hello\1b/g' '{}' +
$ cat f1 f2
f1: hello{}b
f2: hello{}b
Or, more simply (as noted in the comments):
find "$dir" -type f -exec sed -i 's/\(hello[{]}\)a/\1b/g' {} +
Note that the -i
option for Sed is not portable and will not work everywhere. The given command will work on GNU Sed only.
For details, see:
Best Answer
The curly braces will be replaced by the results of the
find
command, and thechmod
will be run on each of them. The+
makesfind
attempt to run as few commands as possible (so,chmod 775 file1 file2 file3
as opposed tochmod 755 file1
,chmod 755 file2
,chmod 755 file3
). Without them the command just gives an error. This is all explained inman find
: