Aquí está mi propia solución para eso.
NOTA: No soy un gran amante de la portabilidad cuando se trata de shell y utilidades, por lo que posiblemente depende en gran medida de Bash 4 y GNU find.
Código
#!/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
}
Explicación
La find
línea de comando resultante se construye como una cadena de -predicates -actions -or
secuencias.
Esto significa lo siguiente: para cada ruta, si tiene -predicates
éxito, haga -actions
, de lo contrario continúe con la siguiente secuencia. El último elemento de la cadena es justo -actions
, que es el caso predeterminado.
Aquí, estoy haciendo -prune
todos los parches encontrados directamente en $EXCEPTIONS
. Esto deja find
de descender más allá de estos nombres.
A continuación, no estoy haciendo nada por todos los padres de padres $EXCEPTIONS
. No queremos eliminar los directorios principales de excepciones, ya que la eliminación es recursiva.
Finalmente, estoy alimentando todos los parches restantes (el caso predeterminado) a xargs rm -r
. Esto es más rápido que -exec rm -r {} \;
porque solo se rm
generará uno .
También lo hago -prune
por ellos porque no tiene sentido eliminarlos explícitamente ./A/B/C
si vamos a eliminarlos ./A/B
.
PD: esto terminó en mi biblioteca de fragmentos :)
./B
y, por lo tanto, lo eliminará a./B/A
pesar de que esté en la lista "ignorar".