Вот мое собственное решение этого.
ПРИМЕЧАНИЕ: я не очень люблю переносимость, когда дело доходит до оболочки и утилит, так что, возможно, это сильно зависит от 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: это закончилось в моей библиотеке фрагментов :)