(Your question is not clear: if a directory contains some.foo
and some.bar
, should it be deleted? I interpreted it as requiring such a directory to be kept.)
The following script should work, provided that no file name contains a newline and no directory matches *.foo
. The principle is to traverse the directory from the leaves up (-depth
), and as *.foo
files are encountered, the containing directory and all parents are marked as protected
. Any reached file that is not *.foo
and not protected is a directory to be deleted. Because of the -depth
traversal order, a directory is always reached after the *.foo
files that might protect it. Warning: minimally tested, remove the echo
at your own risk.
find . -depth -name '*.foo' -o -type d | awk '{
if ($0 ~ /\.foo$/) {
while (sub("/+[^/]+$", "")) {protect[$0]=1;}
} else {
if (!protect[$0]) {
gsub("[\\\001-/]", "\\\\&"); # protect file names for xargs
print;
}
}
}' | xargs echo rm -rf
For once, I'm not proposing a zsh solution.
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
Best Answer
You are making this far more complicated than needed. You don't seem to want to recurse into subdirectories, so all you need to find those directories that don't have a specific file is:
And, to see which of those do have another file:
Or, in a slightly clearer syntax:
This is all done using the built-in tools of the shell. Specifically:
[
: this, and the accompanying]
are synonyms for thetest
builtin (seehelp [
orhelp test
if using an sh-style shell). They are ways of writing test operations in the shell.-e
: this tests whether a file/directory etc exists. Seehelp test
. The simple format is:[ -e file ]
which will return true iffile
exists.[ ! -e filename ]
: the!
simply inverses the test. So[ ! -e file ]
will be true iffile
does not exist.Taken together, this means the command above does:
To run this, of course, you need to first set
$filename1
and$filename2
accordingly. For example: