¿Cómo eliminar todos los directorios vacíos en un subárbol?

151

¿Cómo puedo eliminar todos los directorios vacíos en un subárbol? Usé algo como

find . -type d -exec rmdir {} 2>/dev/null \;

pero necesito que me ejecuten varias veces para eliminar directorios que solo contienen directorios vacíos. Además, es bastante lento, especialmente bajo cygwin.

maaartinus
fuente
Consulte también emacs.stackexchange.com/q/12190/2264 para obtener una solución de emacs.
Sean Allred

Respuestas:

222

Combinando findopciones y predicados de GNU , este comando debería hacer el trabajo:

find . -type d -empty -delete
  • -type d se restringe a los directorios
  • -empty se restringe a los vacíos
  • -delete elimina cada directorio

El árbol se camina desde las hojas sin la necesidad de especificar, -depthya que está implícito -delete.

Christophe Drevet-Droguet
fuente
2
-deleteya implica, -depthpor lo que no necesita especificarlo manualmente.
jamadagni
1
Gracias, no me di cuenta de eso. Respuesta actualizada
Christophe Drevet-Droguet
11
Agregaría -mindepth 1aquí, para evitar eliminar el directorio de inicio en sí, si estuviera vacío.
Greg Dubicki
2
Genial, pero no funciona en mis viejos hosts SunOS ...
dokaspar
2
!tiene un significado especial para la concha. Necesitas escapar de eso. Algo así como: \! -name 'Completed'justo antes -deletedebería funcionar. O simplemente coloca un archivo marcador en este directorio.
Christophe Drevet-Droguet
53

Enumere los directorios profundamente anidados primero.

find . -depth -type d -exec rmdir {} \; 2>/dev/null

(Tenga en cuenta que la redirección se aplica al findcomando como un todo, no solo a rmdir. Redirigir solo para rmdircausaría una desaceleración significativa ya que necesitaría invocar un shell intermedio).

Puede evitar ejecutarse rmdiren directorios no vacíos pasando el -emptypredicado para buscar. GNU find prueba el directorio cuando está a punto de ejecutar el comando, por lo que se recogerán los directorios que se acaban de vaciar.

find . -depth -type d -empty -exec rmdir {} \;

Otra forma de acelerar sería agrupar las rmdirinvocaciones. Es probable que ambos sean notablemente más rápidos que el original, especialmente bajo Cygwin. No espero mucha diferencia entre estos dos.

find . -depth -type d -print0 | xargs -0 rmdir 2>/dev/null
find . -depth -type d -exec rmdir {} + 2>/dev/null

El método más rápido depende de cuántos directorios no vacíos tenga. No puede combinarse -emptycon métodos para agrupar invocaciones, porque los directorios que solo contienen directorios vacíos no están vacíos cuando los findmira.

Otro método sería ejecutar múltiples pases. Si esto es más rápido depende de muchas cosas, incluso si toda la jerarquía de directorios puede permanecer en el caché del disco entre findejecuciones.

while [ -n "$(find . -depth -type d -empty -print -exec rmdir {} +)" ]; do :; done

Alternativamente, use zsh. El calificador glob F coincide con directorios no vacíos, por lo que /^Fcoincide con directorios vacíos. Los directorios que solo contienen directorios vacíos no pueden coincidir tan fácilmente.

while rmdir **/*(/N^F); do :; done

(Esto termina cuando rmdirrecibe una línea de comando vacía).

Gilles
fuente
Eso es. En lugar de 90 segundos, tarda 0,90 s.
maaartinus
@maaartinus: Tengo curiosidad: ¿tienes un conjunto de datos similar donde podrías probar sin él -p? No hubiera pensado que haría la diferencia.
Gilles
3
@maartinus - otras pequeñas optimizaciones: agregar -emptydebería funcionar con esta (aunque no estoy seguro exactamente cuánto ganará). Y muy, muy trivialmente, ya que probablemente no quieras eliminarlo ., úsalo -mindepth 1.
mattdm
No fue la eliminación, sino que el proceso se inició por encima, lo que llevó casi todo el tiempo. Había pasado por alto el -depthargumento, que hace rmdir -pinútil. Ya he cambiado mi comentario. Los años 90 fueron mi intento original; No hay nada sorprendente aquí.
maaartinus
2
Me di cuenta de que podemos eliminar la rmdirllamada de comando por completo, al menos con GNU find, con este comando:find . -depth -type d -empty -delete
Christophe Drevet-Droguet
6

Si solo agregas un -pa tu rmdir, eso funcionará en una sola pasada. No será bonito u óptimo, pero debería tenerlo todo. Eso le dice a rmdir que elimine los directorios principales no vacíos del que está eliminando.

Puede ahorrar un poco agregando la -emptyprueba para buscar, para que no se moleste con directorios no vacíos.

mattdm
fuente
3

find . -depth -type d -exec rmdir {} +

es la respuesta más simple y estándar a esta pregunta.

Desafortunadamente, las otras respuestas dadas aquí dependen de mejoras específicas del proveedor que no existen en todos los sistemas.

astuto
fuente
3
Esta respuesta genera un error para cada directorio que no se puede eliminar y que puede ser menos que deseable.
Willem van Ketwich el
0

find . -type d -printf "%d %p\n" |\ sort -nr |\ perl -pe 's/^\d+\s//;' |\ while read dir; do \ (rmdir "$dir" > /dev/null 2>&1); \ done

Así es como funciona:

  1. Enumere recursivamente todos los directorios junto con su profundidad
  2. Ordenar por orden descendente de su profundidad
  3. Filtre solo las rutas de directorio
  4. Corre rmdiren la lista uno por uno
Ashish Ranjan
fuente
0

Utilizo estos alias para los findcomandos de uso frecuente , especialmente cuando limpio el espacio en disco usando dupeguru , donde eliminar duplicados puede generar muchos directorios vacíos.

Comentarios en el interior .bashrcpara no olvidarlos más tarde cuando necesite modificarlo.

# find empty directories
alias find-empty='find . -type d -empty'

# fine empty/zero sized files
alias find-zero='find . -type f -empty'

# delete all empty directories!
alias find-empty-delete='find-empty -delete'

# delete empty directories when `-delete` option is not available.
# output null character (instead of newline) as separator. used together
# with `xargs -0`, will handle filenames with spaces and special chars.
alias find-empty-delete2='find-empty -print0 | xargs -0 rmdir -p'

# alternative version using `-exec` with `+`, similar to xargs.
# {}: path of current file
# +: {} is replaced with as many pathnames as possible for each invocation.
alias find-empty-delete3='find-empty -exec rmdir -p {} +'

# for removing zero sized files, we can't de-dupe them automatically
# since they are technically all the same, so they are typically left
# beind. this removes them if needed.
alias  find-zero-delete='find-zero -delete'
alias find-zero-delete2='find-zero -print0 | xargs -0 rm'
alias find-zero-delete3='find-zero -exec rm {} +'
raychi
fuente
-2

rm -r */El comando funcionó fácilmente para mí. rmdebería requerir la -feliminación forzosa de directorios con archivos. rm -rsolo debe eliminar directorios vacíos. Estoy abierto a por qué esto podría estar mal. Esto también debería dejar archivos, ya que */solo mira las carpetas.

libroman2
fuente
1
Recomiendo encarecidamente probarlo a fondo primero, ya que rmestá destinado principalmente a eliminar archivos. Si bien */solo coincide con directorios, no tengo idea de lo que hace en niveles más profundos. También puedo imaginar que solo funciona en algunos sistemas.
maaartinus