How to find directories containing only specific files

find

I have several directories with useless files (like *.tmp, desktop.ini, Thumbs.db, .picasa.ini).

How to scan all drives to find directories which contain nothing but some of those files?

Best Answer

To find all directories that contain no other name than *.tmp, desktop.ini, Thumbs.db, and/or .picasa.ini:

find . -type d -exec bash -O dotglob -c '
    for dirpath do
        ok=true
        seen_files=false
        set -- "$dirpath"/*
        for name do
            [ -d "$name" ] && continue  # skip dirs
            seen_files=true
            case "${name##*/}" in
                *.tmp|desktop.ini|Thumbs.db|.picasa.ini) ;; # do nothing
                *) ok=false; break
            esac
        done

        "$seen_files" && "$ok" && printf "%s\n" "$dirpath"
    done' bash {} +

This would use find to locate any directories beneath the current directory (including the current directory) and pass them to a shell script.

The shell script iterates over the given directory paths, and for each, it expands * in it (with the dotglob shell option set in bash to catch hidden names).

It then goes through the list of resulting names and matches them against the particular patterns and names that we'd like to find (ignoring directories). If it finds any other name that doesn't match our list, it sets ok to false (from having been true) and breaks out of that inner loop.

The seen_files variable becomes true as soon as we've seen a file of any type other than directory (or symlink to directory). This variable helps us avoid reporting subdirectories that only contain other subdirectories.

It then runs $seen_files and $ok (true or false) and if these are both true, which means that the directory contains at least one regular file, and only contains filenames in our list, it prints the pathname of the directory.

Instead of

set -- "$dirpath"/*
for name do

you could obviously do

for name in "$dirpath"/*; do

instead.

Testing:

$ tree
.
`-- dir
    |-- Thumbs.db
    `-- dir
        |-- file.tmp
        `-- something

2 directories, 3 files

(find command is run here, producing the output...)

./dir

This means that the directory ./dir only contains names in the list (ignoring directories), while ./dir/dir contains other things as well.

If you remove [ -d "$name" ] && continue from the code, the ./dir directory would not have been found since it contains a name (dir) that is not in our list.

Related Question