Eliminar todos los archivos excepto en un determinado subdirectorio con find

11

Quiero eliminar recursivamente todos los archivos a los que no se accede desde hace un tiempo en la carpeta a, excepto todos los archivos en la subcarpeta b.

find a \( -name b -prune \) -o -type f -delete

Sin embargo, recibo un mensaje de error:

find: la acción -delete se activa automáticamente -depth, pero -prune no hace nada cuando -depth está vigente. Si desea continuar de todos modos, use explícitamente la opción -depth.

Agregar -depthhace bque se incluyan todos los archivos , lo que no debe suceder.

¿Alguien sabe una forma segura de hacer que esto funcione?

Outrin
fuente
@ MichaelKjörling: He echado un vistazo a extglob, pero ¿cómo se incluye todo aexcepto a/b?
2013
No va a cd a && ls -d !(b/*)funcionar? (Para hacerlo, solo en rm -rlugar de ls -d).
un CVn
Su sugerencia elimina subcarpetas. Quiero mantener las carpetas intactas. Quiero encontrar y eliminar todos los archivos en el árbol debajo a(excepto los archivos debajo a/b).
2013
Así que solo omita el -rrm. Parece que lo que estás preguntando es respondido con bastante facilidad usando el globbing extendido de bash, y luego lo que hagas con el resultado del globbing depende de ti.
un CVn
@ MichaelKjörling Solo porque los dos problemas tienen soluciones que se parecen vagamente no hace que las preguntas sean un duplicado. La mayoría de las soluciones a cada uno de los dos problemas no resuelven el otro problema.
Gilles 'SO- deja de ser malvado'

Respuestas:

13

TL; DR: la mejor manera es usar en -exec rmlugar de -delete.

find a \( -name b -prune \) -o -type f -exec rm {} +

Explicación:

¿Por qué encuentra queja cuando intentas usar -deletecon -prune?

Respuesta corta: porque -deleteimplica -depthy -depthhace -pruneineficaz.

Antes de llegar a la respuesta larga, primero observe el comportamiento de encontrar con y sin -depth:

$ find foo/
foo/
foo/f1
foo/bar
foo/bar/b2
foo/bar/b1
foo/f2

No hay garantía sobre el pedido en un solo directorio. Pero hay una garantía de que un directorio se procesa antes de su contenido. Nota foo/antes foo/*y foo/barantes de cualquiera foo/bar/*.

Esto se puede revertir con -depth.

$ find foo/ -depth
foo/f2
foo/bar/b2
foo/bar/b1
foo/bar
foo/f1
foo/

Tenga en cuenta que ahora todos foo/*aparecen antes foo/. Lo mismo con foo/bar.

Respuesta más larga:

  • -pruneevita que find descienda a un directorio. En otras palabras, -pruneomite el contenido del directorio. En su caso, -name b -pruneevita que find descienda a cualquier directorio con el nombre b.
  • -depthhace que find procese el contenido de un directorio antes que el directorio mismo. Eso significa que para cuando find procesa la entrada del directorio, bsu contenido ya ha sido procesado. Por -prunelo tanto, es ineficaz con -depthefecto.
  • -deleteimplica -depthpara que pueda eliminar primero los archivos y luego el directorio vacío. -deletese niega a eliminar directorios no vacíos. Supongo que sería posible agregar una opción para forzar la -deleteeliminación de directorios no vacíos y / o para evitar -deleteque esto implique -depth. Pero esa es otra historia.

Hay otra forma de lograr lo que quieres:

find a -not -path "*/b*" -type f -delete

Esto puede o no ser más fácil de recordar.

Este comando aún desciende al directorio by procesa cada archivo solo para -notrechazarlos. Esto puede ser un problema de rendimiento si el directorio bes enorme.

-pathfunciona de manera diferente -name. -namesolo coincide con el nombre (del archivo o directorio) mientras -pathcoincide con la ruta completa. Por ejemplo, observar el camino /home/lesmana/foo/bar. -name -barcoincidirá porque el nombre es bar. -path "*/foo*"coincidirá porque la cadena /fooestá en la ruta. -pathtiene algunas complejidades que debes entender antes de usarlo. Lea la página de manual de findpara más detalles.

Tenga en cuenta que esto no es 100% infalible. Hay posibilidades de "falsos positivos". La forma en que el comando se escribe arriba omitirá cualquier archivo que tenga algún directorio padre cuyo nombre comience con b(positivo). Pero también omitirá cualquier archivo cuyo nombre comience bindependientemente de la posición en el árbol (falso positivo). Esto se puede solucionar escribiendo una mejor expresión que "*/b*". Eso se deja como ejercicio para el lector.

Supongo que usaste ay bcomo marcadores de posición y los nombres reales son más como allosaurusy brachiosaurus. Si lo coloca brachiosaurusen su lugar b, la cantidad de falsos positivos se reducirá drásticamente.

Al menos los falsos positivos no se eliminarán, por lo que no será tan trágico. Además, puede verificar si hay falsos positivos ejecutando primero el comando sin -delete(pero recuerde colocar el implícito -depth) y examinar la salida.

find a -not -path "*/b*" -type f -depth
lesmana
fuente
-not -pathfue justo la cosa! Gracias por una generosa explicación!
2013
1
Un poco de explicación sobre por qué -not -pathfunciona mientras -pruneque no es útil. ¿Por qué puede -not -pathcoexistir con -depth?
Faheem Mitha
3

Solo use en rmlugar de -delete:

find a -name b -prune -o -type f -exec rm -f {} +
Stéphane Chazelas
fuente
1
¿Puedes explicar por qué rmfunciona y deleteno funciona?
Faheem Mitha
1
Oh, supongo que tal vez porque "-delete se niega a eliminar directorios no vacíos", para citar a @lesmana. Entonces se niega a eliminar directorios no vacíos. Pero rmno tiene ese problema. Pero, independientemente, la elaboración sería algo bueno.
Faheem Mitha
@FaheemMitha, la respuesta a eso está en la pregunta. -deleteimplica -depth, que obviamente no puede funcionar -prune. -pathfunciona, pero no deja findde descender en directorios que no necesita explorar.
Stéphane Chazelas
0

Las respuestas y explicaciones anteriores fueron muy útiles.

Utilizo las soluciones de "-exec rm {} +" o "-not -path ... -delete ', pero pueden ser mucho más lentas que" find ... -delete ". He visto" find ... -delete "ejecutar 5 veces más rápido que" -exec rm {} + "en directorios profundos en un sistema de archivos NFS.

La solución '-not path "tiene la sobrecarga obvia de mirar todos los archivos en los directorios excluidos y debajo.

El "find .. -exec rm {} +" llama a rm que llama al sistema:

fstatat(AT_FDCWD, path...); 
unlinkat(AT_FDCWD, path, 0)

El "find -delete" hace llamadas al sistema:

 fd=open(dir,...);
 fchdir(fd); 
 fstatat(AT_FDCWD, filename,...)
 unlinkat(dirfd, filename,...)

Entonces, el comando "-exec rm {} +" rm realiza la ruta completa a la búsqueda de inodo dos veces por archivo, pero "find -delete" hace una estadística y desvincula el nombre del archivo en el directorio actual. Esa es una gran victoria cuando está eliminando muchos archivos en un directorio.

(modo de quejas activado (lo siento))

Parece que el diseño de la interacción entre -depth, -delete y -prune elimina innecesariamente la forma más eficiente de realizar la acción común "eliminar archivos excepto aquellos en directorios -prune"

La combinación de "-type f -delete" debería poder ejecutarse sin -depth ya que no está tratando de eliminar directorios. Alternativamente, si "find" tenía una acción "-deletefile" que dice que no elimine directorios, no sería necesario implicar -depth.

Las órdenes xargs o find -exec para el comando rm podrían acelerarse si rm tuviera la opción de ordenar nombres de archivos, abrir directorios y hacer unlinkat (dir_fd, filename) en lugar de desvincular las rutas completas. Ya hace el unlinkat (dir_fd, filename) cuando recurre a través de directorios con la opción -r.

(modo gemido apagado)

Donald Mears
fuente