Another POSIX variant with a bit more safeguards:
find . -type d -exec sh -c '
for dir do
contents=$(ls -AFq "$dir") &&
case $contents in
("" | .[dD][sS]_[sS][tT][oO][rR][eE]) printf "%s\n" "$dir"
esac
done' sh {} +
Here:
- we bail out if
ls
can't list the directory contents (or can't find out the type of the files within).
- by using
ls -F
, we make sure we don't list a directory that contains a .DS_Store
that is not a regular file (as -F
adds suffixes for other types of files).
- With
-q
, we avoid false positives with files names $'.DS_Store\n\n'
for instance.
- We match case insensitively as I believe filenames are case insensitive in macos.
A zsh
equivalent could be:
empty() {
set -o localoptions -o extendedglob
ERRNO=0
() ((!ERRNO && !$#)) $REPLY/(#i){^.ds_store(NDY1),.ds_store(N^.)}
}
print -rC1 -- **/*(ND/+empty)
Now, if the point is to remove those directories, there's the case where dirA
contains only dirB
and dirA/dirB
is empty or only contains a .DS_Store
file. Then, we wouldn't be reporting dirA
even though it would become empty after dirA/dirB
is removed.
To address that, that's where you would use -depth
, but you wouldn't be able to use -exec ... {} +
as it's important the empty directories are deleted as soon as they're found, so we'd use -exec ... {} ';'
instead:
find . -depth -type d -exec sh -c '
contents=$(ls -AFq "$1") &&
case $contents in
("" | .[dD][sS]_[sS][tT][oO][rR][eE]) exec rm -rf "$1"
esac' sh {} ';'