First, why your attempt doesn't work: -printf "%h\n"
prints the directory part of the .avi
file name. That doesn't affect anything in the subsequent -exec
action — {}
doesn't mean “whatever the last printf
command printed”, it means “the path to the found file”.
If you want to use the directory part of the file name in that cp
command, you need to modify the file name before passing it to cp
. The find
command doesn't have this capability, but a shell does, so make find
invoke a shell which invokes cp
.
find . -name "*.avi" -exec sh -c 'cp -Rp "${0%/*}" /share/USBDisk1/Movies/' {} \;
Note that you'll need to pass -r
to cp
since you're copying a directory. You should probably preserve metadata such as the files' modification time, too, with -p
.
You may usefully replace cp -Rp
by rsync -a
. That way, if you've already copied a movie directory, it won't be copied again (unless its contents have changed).
Your command has a defect that may or may not affect you: if a directory contains multiple .avi
files, it will be copied multiple times. It would be better to look for directories, and copy them if they contain a .avi
file, rather than look for .avi
files. If you use rsync
instead of cp
, the files won't be copied again, it's just a bit more work for rsync
to verify the existence of the files over and over.
If all the movie directories are immediately under the toplevel directory, you don't need find
, a simple loop over a wildcard pattern suffices.
for d in ./*/; do
set -- "$d/"*.avi
if [ -e "$1" ]; then
# there is at least one .avi file in $d
cp -rp -- "$d" /share/USBDisk1/Movies/
fi
done
If the movie directories may be nested (e.g. Movies/science fiction/Ridley Scott/Blade Runner
), you don't need find
, a simple loop over a wildcard pattern suffices. You do need to be running ksh93 or bash ≥4 or zsh, and in ksh93 you need to run set -o globstar
first, and in bash you need to run shopt -s globstar
first. The wildcard pattern **/
matches the current directory and all its subdirectories recursively (bash also traverses symbolic links to subdirectories).
for d in ./**/; do
set -- "$d/"*.avi
if [ -e "$1" ]; then
cp -rp -- "$d" /share/USBDisk1/Movies/
fi
done
The easy way is with zsh. You can use glob qualifiers to match files according to criteria such as their type and size. The wildcard pattern **/
matches any level of subdirectories. The history modifiers h
and t
are easy ways of extracting the directory and the base part of a filename. Call mkdir -p
to create the directories when needed.
cd dirA
for x in **/*(.Lm+300); do
mkdir -p ../dirB/$x:h &&
mv -- $x ../dirB/$x
done
The portable way is with find
. Use -exec
to invoke a shell snippet for every file.
cd dirA
find . -type f -size +300000k -exec sh -c 'for x do
mkdir -p "../dirB/${x%/*}"
mv "$x" "../dirB/$x"
done' sh {} +
Parallelization is rarely useful for input/output: it lets you take advantage of multiple CPUs but the CPU is rarely a bottleneck in I/O.
Best Answer
you can use find with xargs for this
find /thisdir -type f -name "*.ogg" -print0 | xargs -0 -Imysongs mv -i mysongs /somedir
OR
In your command just try to move '{}' after
mv
command.find /thisdir -type f -name '*.ogg' -exec mv -i {} /somedir \;