1

Эта проблема

У меня есть структура каталогов, например, что-то вроде этого (здесь конечные слеши представляют папки):

./A
./B/A/A
./B/A/B
./B/B/
./B/C
./C/

И мне нужно рекурсивно удалить все, кроме некоторых файлов и каталогов:

./A
./B/A

После выполнения команды / скрипта, который я ищу, я хочу получить следующую иерархию:

./A
./B/A/A
./B/A/B

Попытка решения

Я попытался использовать find (-print является заполнителем):

find \( -path ./A -or -path ./B/A \) -prune -or -print

Это не работает, поскольку удаляет родительские каталоги записей в списке "не трогать":

$ find \( -path ./A -or -path ./B/A \) -prune -or -print
.
./B
./B/B
./B/C
./C

Особенно это удаляет ./B то время как мне нужно сохранить ./B/A Черт возьми, он удаляет текущий каталог, в конце концов.

Я хочу избежать рекурсивных вызовов (например, find -exec something-that-calls-find.sh), так как списки каталогов, которые я буду обрабатывать, довольно большие ...

2 ответа2

1

Я думаю, что проще всего использовать регулярное выражение для сопоставления путей

  • ./B/A
  • ./B/A/A
  • ./B/A/B
  • ./B/A/B/C
  • и так далее

Итак, следующее будет соответствовать ./A или что-нибудь ниже ./B/A , включая ее. Я добавил \ чтобы сделать команду более читабельной. Также обратите внимание, что это работает только с GNU find , т.е. не с BSD find .

find -depth -regextype posix-extended -mindepth 1 \
! \( -path "./A" -or -regex "\./B(/A(/.*)?)?" \)

Чтобы объяснить регулярное выражение: /.* соответствует чему-либо в каталоге A Здесь вам понадобится косая черта, потому что в противном случае каталог с именем AB также соответствовал бы. Этот предыдущий шаблон может появляться ноль раз (для каталога A) или один раз (для всего, что ниже A), поэтому нам нужен ? , Поскольку мы не хотим удалять B , часть после нее может появиться ноль или один раз (?).

Поскольку есть отрицание (!), Команда find будет соответствовать:

./B/B
./B/C
./C

Затем вы можете добавить опцию -exec rm -rf {} чтобы удалить эти файлы и папки. Нам нужна опция -depth чтобы начать с самой глубокой, чтобы не пытаться удалять папки, которые больше не существуют.

1

Вот мое собственное решение этого.
ПРИМЕЧАНИЕ: я не очень люблю переносимость, когда дело доходит до оболочки и утилит, так что, возможно, это сильно зависит от Bash 4 и GNU find.

Код

#!/bin/bash

## given "a/b/c/d", prints "a/b/c", "a/b" and "a".
# $1...: pathes to process
function get_parent_directories() {
    local CURRENT_CHUNK

    for arg; do
        CURRENT_CHUNK="$arg"

        while true; do
            CURRENT_CHUNK="$(dirname "$arg")"
            [[ "$CURRENT_CHUNK" == "." ]] && break
            echo "$CURRENT_CHUNK"
        done
    done
}

## recursively removes all files in given directory, except given names.
# $1: target directory
# $2...: exceptions
function remove_recursive() {
    local DIR="$1"
    shift
    local EXCEPTIONS=( "$@" )

    # find all files in given directory...
    local FIND_ARGS=( find "$DIR" -mindepth 1 )

    # ...skipping all exceptions and below...
    for file in "${EXCEPTIONS[@]}"; do
        FIND_ARGS+=( -path "$file" -prune -or )
    done

    # ...and ignoring all parent directories of exceptions (to avoid removing "./B" when "./B/A" is an exception)...
    while read file; do
        FIND_ARGS+=( -path "$file" -or )
    done < <(get_parent_directories "${EXCEPTIONS[@]}" | sort -u)

    # ...and printing all remaining names, without their descendants (we're going to recursively remove these anyway).
    FIND_ARGS+=( -print0 -prune )

    "${FIND_ARGS[@]}" | xargs -r0 rm -r
}

объяснение

Результирующая командная строка find строится как цепочка -predicates -actions -or последовательностей.

Это означает следующее: для каждого пути, если -predicates успешно, do -actions , в противном случае перейти к следующей последовательности. Последний элемент в цепочке это просто -actions , что является случаем по умолчанию.

Здесь я делаю -prune для всех патчей, найденных в $EXCEPTIONS . Это перестает find спускаясь за эти имена.

Далее я ничего не делаю для всех родителей патчей в $EXCEPTIONS . Мы не хотим удалять родительские каталоги исключений, так как удаление является рекурсивным.

Наконец, я передаю все оставшиеся пути (случай по умолчанию) в xargs rm -r . Это просто быстрее, чем -exec rm -r {} \; потому что только один rm будет порожден.

Я также делаю -prune для них, потому что нет смысла явно удалять ./A/B/C если мы собираемся удалить ./A/B

PS: это закончилось в моей библиотеке фрагментов :)

Всё ещё ищете ответ? Посмотрите другие вопросы с метками .