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.
-path
works exactly like -name
, but applies the pattern to the entire pathname of the file being examined, instead of to the last component.
-prune
forbids descending below the found file, in case it was a directory.
Putting it all together, the command
find $HOME -path $HOME/$dir_name -prune -o -name "*$file_suffix" -exec cp {} $HOME/$dir_name/ \;
- Starts looking for files in
$HOME
.
- If it finds a file matching
$HOME/$dir_name
it won't go below it ("prunes" the subdirectory).
- Otherwise (
-o
) if it finds a file matching *$file_suffix
copies it into $HOME/$dir_name/
.
The idea seems to be make a backup of some of the contents of $HOME
in a subdirectory of $HOME
. The parts with -prune
is obviously necessary in order to avoid making backups of backups...
Best Answer
This finds files in the current directory whose basename begins or is the string
CMakeLists
. The argument is escaped (double quoted) so that the shell doesn't expand it beforefind
runs.There is no need to add
cat
with a pipe togrep
--- it's a useless process with useless IO, here. Adding/dev/null
insures thatgrep
will report the filename along with the matching line(s) when there is more than one file to match.By using
{} +
as the terminating sequence to the-exec
argument, multiple filenames are passed to each invocation of thegrep
command. Had we used{} \;
then agrep
process would have been spawned for every file found. Needless process instantiation is expensive if done hundreds or thousands of times.To use a pipe with a
find
-exec
argument, you need to invoke the shell. A contrived example could be togrep
for the string "one" but only if the string "two" isn't present too. This could be done as:This is predicated on the comments, below, by @muru, @Serg and @Scott, with thanks.