Next: Querying, Previous: Single File, Up: Run Commands
Sometimes you need to process files one at a time. But usually this is not necessary, and, it is faster to run a command on as many files as possible at a time, rather than once per file. Doing this saves on the time it takes to start up the command each time.
The ‘-execdir’ and ‘-exec’ actions have variants that build command lines containing as many matched files as possible.
This works as for ‘-execdir command ;’, except that the ‘{}’ at the end of the command is expanded to a list of names of matching files. This expansion is done in such a way as to avoid exceeding the maximum command line length available on the system. Only one ‘{}’ is allowed within the command, and it must appear at the end, immediately before the ‘+’. A ‘+’ appearing in any position other than immediately after ‘{}’ is not considered to be special (that is, it does not terminate the command).
This insecure variant of the ‘-execdir’ action is specified by POSIX. The main difference is that the command is executed in the directory from which
find
was invoked, meaning that ‘{}’ is expanded to a relative path starting with the name of one of the starting directories, rather than just the basename of the matched file.
Before find
exits, any partially-built command lines are
executed. This happens even if the exit was caused by the
‘-quit’ action. However, some types of error (for example not
being able to invoke stat()
on the current directory) can cause
an immediate fatal exit. In this situation, any partially-built
command lines will not be invoked (this prevents possible infinite
loops).
At first sight, it looks like the list of filenames to be processed
can only be at the end of the command line, and that this might be a
problem for some comamnds (cp
and rsync
for example).
However, there is a slightly obscure but powerful workarouund for this
problem which takes advantage of the behaviour of sh -c
:-
find startpoint -tests ... -exec sh -c 'scp "$@" remote:/dest' sh {} +
In the example above, the filenames we want to work on need to occur
on the scp
command line before the name of the destination. We
use the shell to invoke the command scp "$@" remote:/dest
and
the shell expands "$@"
to the list of filenames we want to
process.
Another, but less secure, way to run a command on more than one file
at once, is to use the xargs
command, which is invoked like
this:
xargs [option...] [command [initial-arguments]]
xargs
normally reads arguments from the standard input. These
arguments are delimited by blanks (which can be protected with double
or single quotes or a backslash) or newlines. It executes the
command (default is /bin/echo) one or more times with any
initial-arguments followed by arguments read from standard
input. Blank lines on the standard input are ignored. If the
‘-L’ option is in use, trailing blanks indicate that xargs
should consider the following line to be part of this one.
Instead of blank-delimited names, it is safer to use ‘find
-print0’ or ‘find -fprint0’ and process the output by giving the
‘-0’ or ‘--null’ option to GNU xargs
, GNU tar
,
GNU cpio
, or perl
. The locate
command also has a
‘-0’ or ‘--null’ option which does the same thing.
You can use shell command substitution (backquotes) to process a list of arguments, like this:
grep -l sprintf `find $HOME -name '*.c' -print`
However, that method produces an error if the length of the ‘.c’
file names exceeds the operating system's command line length limit.
xargs
avoids that problem by running the command as many times
as necessary without exceeding the limit:
find $HOME -name '*.c' -print | xargs grep -l sprintf
However, if the command needs to have its standard input be a terminal
(less
, for example), you have to use the shell command
substitution method or use the ‘--arg-file’ option of
xargs
.
The xargs
command will process all its input, building command
lines and executing them, unless one of the commands exits with a
status of 255 (this will cause xargs to issue an error message and
stop) or it reads a line contains the end of file string specified
with the ‘--eof’ option.