Trailing Arguments with Find -Exec {} +

Trailing arguments with find -exec {} +

find . -exec sh -c 'echo "$@" asd' _ {} +

find -exec with \; runs but + terminator fails with missing argument to -exec

In find -exec ... {} +, the + must be immediately after the {} (and thus the inserted arguments must be in trailing position). Your given command does not meet this requirement.


Consider the following workaround:

find . -type f -name "*users*" -cmin -60 \
-exec sh -c 'rsync -avhP -e "ssh -i /home/some_user/.ssh/id_rsa -c arcfour" "$@" some_user@some_host:/some/destination/' _ {} +

Because the sh -c [...] line is longer than the rsync line which it runs, any argument list passed to the former is guaranteed to work for the latter, so expanding "$@" will always succeed.

find -exec' a shell function in Linux

Since only the shell knows how to run shell functions, you have to run a shell to run a function. You also need to mark your function for export with export -f, otherwise the subshell won't inherit them:

export -f dosomething
find . -exec bash -c 'dosomething "$0"' {} \;

Why are the backslash and semicolon required with the find command's -exec option?

The backslash before the semicolon is used, because ; is one of list operators (or &&, ||) for separating shell commands. In example:

command1; command2

The find utility is using ; or + to terminate the shell commands invoked by -exec.

So to avoid special shell characters from interpretation, they need to be escaped with a backslash to remove any special meaning for the next character read and for line continuation.

Therefore the following example syntax is allowed for find command:

find . -exec echo {} \;
find . -exec echo {} ';'
find . -exec echo {} ";"
find . -exec echo {} \+
find . -exec echo {} +

See also:

  • Using semicolon (;) vs plus (+) with exec in find
  • Simple unix command, what is the {} and \; for

How to join the output of two commands when using find -exec?

If you want to run two commands whose output is combined into a single line, you'll need to use -exec to execute a single shell that runs both commands.

find . -exec sh -c \
'for f; do stat --format="%n,%x,%y,%z,$(du -k "$f")" "$f"; done' \
_ {} +

Some notes of explanation:

  1. When using -c, the first argument following the shell command is used to set $0. I usually use a dummy value of _ when I don't care what $0 is set to.
  2. -exec ... {} + will pass as many matches as possible to the command being run.
  3. The loop in the -c argument iterates over the files passed to the shell by find in this invocation.
  4. du -k "$f" is run first to construct the format string to be used when stat ... "$f" is called. (I am assuming that the argument to du will be a single file, not a directory, as otherwise you are going to get a list of results that won't fit nicely into the line you are trying to construct.)

Why does find -exec mv {} ./target/ + not work?

The manual page (or the online GNU manual) pretty much explains everything.

find -exec command {} \;

For each result, command {} is executed. All occurences of {} are replaced by the filename. ; is prefixed with a slash to prevent the shell from interpreting it.

find -exec command {} +

Each result is appended to command and executed afterwards. Taking the command length limitations into account, I guess that this command may be executed more times, with the manual page supporting me:

the total number of invocations of the command will be much less than the number of matched files.

Note this quote from the manual page:

The command line is built in much the same way that xargs builds its command lines

That's why no characters are allowed between {} and + except for whitespace. + makes find detect that the arguments should be appended to the command just like xargs.

The solution

Luckily, the GNU implementation of mv can accept the target directory as an argument, with either -t or the longer parameter --target. It's usage will be:

mv -t target file1 file2 ...

Your find command becomes:

find . -type f -iname '*.cpp' -exec mv -t ./test/ {} \+

From the manual page:

-exec command ;

Execute command; true if 0 status is returned. All following arguments to find are taken to be arguments to the command until an argument consisting of `;' is encountered. The string `{}' is replaced by the current file name being processed everywhere it occurs in the arguments to the command, not just in arguments where it is alone, as in some versions of find. Both of these constructions might need to be escaped (with a `\') or quoted to protect them from expansion by the shell. See the EXAMPLES section for examples of the use of the -exec option. The specified command is run once for each matched file. The command is executed in the starting directory. There are unavoidable security problems surrounding use of the -exec action; you should use the -execdir option instead.

-exec command {} +

This variant of the -exec action runs the specified command on the selected files, but the command line is built by appending each selected file name at the end; the total number of invocations of the command will be much less than the number of matched files. The command line is built in much the same way that xargs builds its command lines. Only one instance of `{}' is allowed within the command. The command is executed in the starting directory.

Multiple find exec options: synology index command line

Your command substitutions $(echo $path) and $(basename "{}") are executed by your shell before find is executed.
And you don't need to echo the $path variable. You could execute a small shell script instead:

find . -type f -name "*.$ext" -exec sh -c '
targetpath=$1; shift # get the value of $path
for file; do
mv -i "$file" "$targetpath"
synoindex -a "$targetpath/${file##*/}"
done
' sh "$path" {} +

This starts find in the current directory . searching for regular files (-type f) ending with
the file extension $ext (-name "*.$ext")
and executes a small shell script passing the $path variable as first argument to the script.
The following arguments are the filepaths found by find.

The parameter expansion ${file##*/} removes the longest prefix */ from the file and the result is the basename.
If your $path variable already contains a trailing slash /, then omit the / after $targetpath.



Related Topics



Leave a reply



Submit