2

I need to search a rather big directory hierarchy for regular files with names matching a particular filename globbing pattern. The hierarchy is so big (both very deep and with some enormous directories) that it would take far too long to take a naive approach:

find /top/dir -type f -name 'pattern'

(Where pattern is some pattern like *proj*.tgz.)

Due to the nature of the directory structure, I know I could introduce an optimization to prune the search tree if find found a file in a directory. For example, finding one or several files in a particular directory would mean that I wouldn't need to examine any of the subdirectories of that specific directory for other matches.

Since applying -prune to a regular file isn't doing the right thing, I can't just do

find /top/dir -type f -name 'pattern' -prune

Question: How do I avoid searching the subdirectories of the directory that contains the file(s) that matches the pattern?

2 Answers 2

1

One could call an in-line script for each directory. The script would check whether the pattern matches any regular files in the directory. If the pattern matches, it outputs (in the general case, processes rather than just print) the matching pathnames and prunes the parent directory from the search tree:

find /top/dir -type d -exec zsh -c '
    set -- "$1"/pattern(.N)
    [[ $# -eq 0 ]] && exit 1
    printf "%s\n" "$@"' zsh {} \; -prune

I'm using the zsh shell for the in-line script to access that shell's globbing qualifiers. The qualifier used here, (.N), ensures that only regular files match the pattern and removes the pattern if there are no matching files.


Using bash for the in-line script:

find /top/dir -type d -exec bash -O nullglob -c '
    unset -v found
    for pathname in "$1"/pattern; do
        if [[ -f "$pathname" ]] && [[ ! -h "$pathname" ]]; then
            printf "%s\n" "$pathname"
            found=true
        fi
    done
    "${found-false}"' bash {} \; -prune

That is, let the in-line script loop over the names matching the pattern in the specific directory, and if any name corresponds to a regular file, process it and set a "flag". If the flag is set at the end, prune the parent directory.

0

Traverse the hierarchy of directories, and in each one prune the tree if the flag file (pattern) is found, but otherwise search for the wanted files (*proj*.tgz)

find /top/dir -type d -exec sh -c 'z=$(find "$@" -maxdepth 1 -type f -name "pattern" -print -quit); [ -n "$z" ]' _ {} \; -prune -o -type f -name '*proj*.tgz' -print

I ended up writing a more complex version of this that allowed me to see what was going on. Obviously I had to change /top/dir, pattern, and *proj*.tgz for items that were relevant locally.) I'll include it here for posterity

find /top/dir -type d \
    -exec bash -c '
        echo "Considering $*";
        z=$(find "$@" -maxdepth 1 -type f -printf "| %p\n" -name "pattern" -printf "Found flag file\n" -quit);
        [[ -n "$z" ]] && echo "$z";
        [[ "$z" =~ "Found flag file" ]] || { echo "No flag found"; exit 1; }
    ' _ {} \; \
    -printf "Pruning tree\n" -prune \
    -o \
    -type f -name '*proj*.tgz' -print

The real solution requires the non-POSIX find -maxdepth. The debugging version also requires non-POSIX find -printf. There is an alternate approach for implementing -maxdepth that satisfies POSIX, but I haven't used it here; the code is opaque enough as it is.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .