Мне нужно искать в довольно большой иерархии каталогов обычные файлы с именами, соответствующими определенному шаблону подстановки имен файлов. Иерархия настолько велика (и очень глубокая, и с некоторыми огромными каталогами), что на наивный подход ушло бы слишком много времени:

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

(Где pattern — это какой-то шаблон, например *proj*.tgz.)

Из-за характера структуры каталогов я знаю, что могу ввести оптимизацию для обрезки дерева поиска, если find нашел файл в каталоге. Например, нахождение одного или нескольких файлов в определенном каталоге означало бы, что мне не нужно проверять какие-либо подкаталоги этого конкретного каталога на наличие других совпадений.

Поскольку применение -prune к обычному файлу не работает правильно, я не могу просто сделать

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

Вопрос: Как избежать поиска в подкаталогах каталога, содержащего файлы, соответствующие шаблону?

2
Kusalananda 11 Дек 2021 в 22:25

2 ответа

Можно вызвать встроенный скрипт для каждого каталога. Сценарий проверит, соответствует ли шаблон каким-либо обычным файлам в каталоге. Если шаблон совпадает, он выводит (в общем случае обрабатывает, а не просто печатает) совпадающие пути и удаляет родительский каталог из дерева поиска:

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

Я использую оболочку zsh для встроенного скрипта для доступа к квалификаторам подстановки этой оболочки. Используемый здесь квалификатор (.N) гарантирует, что шаблону соответствуют только обычные файлы, и удаляет шаблон, если соответствующих файлов нет.


Использование bash для встроенного скрипта:

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

То есть пусть встроенный скрипт зациклится на именах, соответствующих шаблону в конкретном каталоге, и, если какое-либо имя соответствует обычному файлу, обработает его и установит «флаг». Если флаг установлен в конце, удалите родительский каталог.

1
Kusalananda 10 Дек 2021 в 22:03

Пройдите по иерархии каталогов и в каждом обрежьте дерево, если файл флага (pattern) найден, но в противном случае ищите нужные файлы (*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

В итоге я написал более сложную версию этого, которая позволила мне увидеть, что происходит. Очевидно, мне пришлось изменить /top/dir, pattern и *proj*.tgz для элементов, которые были актуальны локально.) Я включу это здесь для потомков.

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

Настоящее решение требует не-POSIX find -maxdepth. Для отладочной версии также требуется не-POSIX find -printf. Существует альтернативный подход для реализации -maxdepth, который удовлетворяет POSIX, но я не использовал его здесь. ; код и так достаточно непрозрачен.

0
roaima 13 Дек 2021 в 15:37