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.
What is this strange syntax inside `find -exec`?
To understand you need to know 4 things:
The
find
action-exec
allows you to apply a command on the found files and directories.The
-c
bash
option is documented as follows:BASH(1)
...
OPTIONS
...
-c If the -c option is present, then commands are read from
the first non-option argument command_string.
If there are arguments after the command_string, they
are assigned to the positional parameters, starting with $0.
...
If bash is started with the -c option, then $0 is set to the first
argument after the string to be executed, if one is present.
Otherwise, it is set to the filename used to invoke bash, as given
by argument zero.In
bash
,$@
expands as all positional parameters ($1
,$2
...) starting at parameter$1
.In a
bash
function, the positional parameters are the arguments passed to the function when it is called.
So, in your case, the command executed for each found file or directory is:
bash -c 'foo "$@"' bash <the-file>
The positional parameters are thus set to:
$0 = bash
$1 = <the-file>
and bash
is asked to execute 'foo "$@"'
in this context. "$@"
is first expanded as "<the-file>"
. So, function foo
is called with one single argument: "<the-file>"
. In the context of function foo
the positional parameters are thus:
$1 = "<the-file>"
and echo "$@"
expands as echo "<the-file>"
.
All this just prints the names of all found files or directories. It is almost as if you had any of:
find . -exec echo {} \;
find . -print
find .
find
(for find
versions that accept the last one).
Almost as if, only, because if file or directory names contain spaces, depending on your use of find
and of quotes, you will get different results. So, if you intend to have a more complex foo
function, you should pay attention to the quotes. Examples:
$ touch "filename with spaces" plain
$ ls -1
filename with spaces
plain # 2 files
$ foo() { echo "$@"; } # print arguments
$ find . -type f
./filename with spaces
./plain
$ find . -type f -exec bash -c 'foo "$@"' bash {} \;
./filename with spaces
./plain
$ find . -type f -exec bash -c 'foo $@' bash {} \;
./filename with spaces
./plain
The 3 find
commands apparently do the same but:
$ bar() { echo $#; } # print number of arguments
$ wc -w < <(find . -type f)
4 # 4 words
$ find . -type f -exec bash -c 'bar "$@"' bash {} \;
1 # 1 argument
1 # per file
$ find . -type f -exec bash -c 'bar $@' bash {} \;
3 # 3 arguments
1 # 1 argument
With find . -type f -exec bash -c 'bar "$@"' bash {} \;
, the first file name is passed to function bar
as one single argument, while in all other cases it is considered as 3 separate arguments.
Why xargs find | xargs mv and xargs find -exec mv work but produce error printouts No such file or directory?
Short answer: find
is being told to search all 4 subdirectories, even after some of them have been deleted.
Detailed eplanation: The root problem is that the wildcard in the xargs -I abc find ./testdir/* -maxdepth 1 ...
part gets expanded by the shell before any of the commands get run. So that part of the script becomes:
... | xargs -I abc find ./testdir/dir1 ./testdir/dir2 ./testdir/dir3 ./testdir/dir4 -maxdepth 1 ...
What happens then is the stat | sed
part sends the first inode number to xargs
, that runs find
, find
searches all 4 directories for the matching item, and either deletes it directly or sends its path to the next xargs
for deletion.
Next, the stat | sed
part sends the second inode number to xargs
, that runs find
, find
searches all 4 directories... oops, hey, one of them's missing! So it prints an error message about ./testdir/dir1 not existing, searches the other three, and (one way or another) deletes the next one.
Next comes the third inode number, and this time neither ./testdir/dir1 nor ./testdir/dir2 exists, so you get two error messages. Etc etc etc.
(There's also an additional problem with the second one, where immediately after running the mv
command, find
then tries to search its contents, and oops it's gone. That's why you get more error messages that way. I think you might've wanted -maxdepth 0
to keep it from trying to do that.)
Solution: I'm not sure what the larger context is, but my immediate reaction is that this is an overcomplex mess and as much as possible should be removed. But without knowing what can be changed without breaking the big picture, the minimal fix I see is to just have find
search the entire testdir directory, rather than using a wildcard to list specific (wrong) subdirectories:
... | xargs -I abc find ./testdir -maxdepth 1 ...
(And note that in this form, -maxdepth 1
is actually correct.)
Move command error target is not directory UNIX
In find -exec
the {}
is replaced by the file/directory found.
The mv
command used as in your example is trying to move the first argument to the second argument so your arguments are in the wrong order.
This would be correct:
[[ -d ~/destination ]] && find ~/testfiles -name '*.c' -exec mv -i '{}' ~/destination \;
A slightly more effetive way would be to use mv
s -t
(target) option which means that you can use the +
in find
(to supply as many arguments to mv
in one go as possible):
[[ -d ~/destination ]] && find ~/testfiles -name '*.c' -exec mv -i -t ~/destination '{}' +
I added [[ -d ~/destination ]] &&
before the find
command since one common mistake is to move a lot of files to the same destination and if it doesn't exist (or is a file and not a directory) it'll create a file named destination
and overwrite that with all the files making you loose all your files (except the last one moved).
Explanation for the shift from +
to \;
in -exec
taken directly from my man find
page:
-exec command ;
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, ... The specified command is run once for each matched file. ...
-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 command line is built in much the same way thatxargs
builds its command lines. Only one instance of{}
is allowed within the command, ...
Note that the syntax differs and that {} +
must be at the end (where it's appending files) for the second version.
exec not working after using find ... -exec grep -L
The problem is that grep -L
is exiting with a return code of 1
for some reason. I can't explain why it is doing that though. My understanding is that it shouldn't be doing that because lines are being output.
In any case that exit status causes the -exec
to evaluate to false
and causes find
to stop processing the file (the same as any other failing test does).
This doesn't happen with +
because when find
gives grep
multiple files at once at least one is matching those contents so grep
exits with a zero return code (but that's obviously not reliable here). I also cannot explain this behavior this seems exactly backwards to me also.
I believe using -not -exec grep -L ... \;
will do what you want but I'd have to think about that a bit more to be sure.
Hm... I suppose the return codes make sense in that when the filename is output no matching lines were found and when the filename is not output matching lines were found.
-exec option of the find command does not work as expected on Ubuntu
The default -and
operation has higher precedence than -or
. Use extra parentheses:
find ~ \( \( -type f -not -perm 0600 \) -or \( -type d -name 'D*' \) \) -exec ls -l '{}' ';'
You can probably omit the inner parentheses in this case.
linux find: move files by xargs
Using find and exec
$ find ~/Desktop -name "*.jpg" -exec mv '{}' /tmp/target/ \; -or -name "*.gif" -exec mv '{}' /tmp/target/ \; -or -name "*.png" -exec mv '{}' /tmp/target/ \;
Using xargs
$ find ~/Desktop -name "*.jpg" -or -name "*.gif" -or -name "*.png" | xargs -I SRCFILE mv SRCFILE /tmp/target/
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
Related Topics
How to Confirm Sftp File Delivery
How to Assign a Name for a Screen
Is There a Linux Command to Determine the Window Ids Associated with a Given Process Id
How to Install Node Binary Distribution Files on Linux
Howto Prepare Qtcreator for Linux Driver & Kernel Development
Using Rsync Filter to Include/Exclude Files
Why Is "Echo Foo | Read a ; Echo $A" Not Working as Expected
Memory Access Error Sys_Rt_Sigaction (Signal Handler)
What Are Good Linux/Unix Books for an Advancing User
How to Check If There Are Symbolic Links Pointing to a Directory
Is There a Limit on Number of Tcp/Ip Connections Between MAChines on Linux
What's the Meaning of a ! Before a Command in the Shell
How to Convert Pptx Files to Jpg or Png (For Each Slide) on Linux