Eliminar todos los archivos excepto el más grande

Eliminar todos los archivos excepto el más grande

Tengo una carpeta con muchas subcarpetas. Quiero eliminar todos los archivos más pequeños de cada subcarpeta, dejando solo el archivo más grande.

Por ejemplo:

Subfolder1
---------- File 1 ---- 300k
---------- File 2 ---- 299k
---------- File 3 ---- 800k

Sólo file 3debería quedar el de 800k. Si la carpeta solo tiene un archivo, permanece.

Este código funciona, pero no puedo ponerlo en un bucle for (para directorio recursivo):

find . -type f -maxdepth 1 | sort -n -r | tail -n +2 | xargs -I{} rm -v {}

¿Cómo puedo hacer esto?

Respuesta1

~$ tree -fQFi --sort=size pluto
"pluto"
"pluto/pluto1"/
"pluto/pluto1/pluto3"/
"pluto/pluto1/pluto3/nozero.txt"
"pluto/pluto1/pluto3/zero ed.txt"
"pluto/pluto1/nozero.txt"
"pluto/pluto2"/
"pluto/pluto2/nozero.txt"
"pluto/pluto2/nozer.txt"
"pluto/pluto2/zero.txt"
"pluto/pluto4"/
"pluto/pluto4/zeroed.txt"
"pluto/zeroed.txt"

4 directories, 8 files

~$ tree -fQFic --noreport --sort=size pluto | \
> awk -F"/" 'NR==1||/\/$/{next}; \
>     {path=""; for(i=1;i<NF;i++) path=path$i; if(a[path]++) print}'
"pluto/pluto1/pluto3/zero ed.txt"
"pluto/pluto2/nozer.txt"
"pluto/pluto2/zero.txt"

~$ tree -fQFic --noreport --sort=size pluto | \
> awk -F"/" 'NR==1||/\/$/{next}; \
>     {path=""; for(i=1;i<NF;i++) path=path$i; if(a[path]++) print}' | \
> xargs rm -v
'pluto/pluto1/pluto3/zero ed.txt' rimosso
'pluto/pluto2/nozer.txt' rimosso
'pluto/pluto2/zero.txt' rimosso

~$ tree -fQFi --sort=size pluto
"pluto"
"pluto/pluto1"/
"pluto/pluto1/pluto3"/
"pluto/pluto1/pluto3/nozero.txt"
"pluto/pluto1/nozero.txt"
"pluto/pluto2"/
"pluto/pluto2/nozero.txt"
"pluto/pluto4"/
"pluto/pluto4/zeroed.txt"
"pluto/zeroed.txt"

4 directories, 5 files

treeenumera por directorio y luego por tamaño descendente.

  • awkLa primera línea de código omite treela primera línea de salida.olíneas con barras al final (es decir, directorios)
  • awkLa segunda línea de código de 'crea un nombre de directorio a partir de la ruta completa ( forbucle), luego imprime los nombres de ruta completos si el nombre de directorio se encontró una vez en las líneas anteriores (es decir, imprime, para cada directorio, desde el segundo archivo listado en adelante)

Respuesta2

Justificación

Este es mi intento de crear un comando que funcione concualquierdirectorio y nombre(s) de archivo. En general, las rutas en Linux (y los nombres en los sistemas de archivos) pueden contener cualquier carácter excepto nulo ( 0x00) y /. Los caracteres problemáticos pueden ser " " (espacio), cualquier otro carácter blanco, 'nueva "línea y otros caracteres no imprimibles. Por lo tanto es importante:

  • abandonar herramientas que reemplazan algunos caracteres con otros (por ejemplo, muchas implementaciones de lswill print ?para no imprimibles);
  • pasar todos los nombres como cadenas terminadas en nulo (elija herramientas que puedan analizarlos);
  • citar adecuadamente.

Me inspiré en la discusión bajoesta otra respuesta.


Comandos reales

Versión de prueba, solo se eliminarán lslos archivos que se eliminarían:

find -type d -exec sh -c 'find "$0" -maxdepth 1 -mindepth 1 -type f -exec stat --printf "%s %n\0" \{\} + | sort -znr | tail -zn +2' {} \; | cut -zf 2- -d " " | xargs -0r ls -l

Sí, lo estoy usando lsaquí a pesar de lo que acabo de decir. Esto se debe a que lsla salida no se analiza más. Lo estoy usando sólo para mostrar el resultado. Si tiene directorios o archivos con caracteres problemáticos en sus nombres, observará lscuyo comportamiento debería convencerlo denunca analizarls(a menos que sepa que está absolutamente seguro con él). Aún así, los nombres problemáticos pasarán hasta el final lsy este es el punto.

Comprender la versión de prueba(ver abajo para alguna explicación)y pruébalo antes de dejar que la versión funcional(justo debajo)elimina tus archivos.Recuerda que soy sólo un tipo cualquiera en Internet.

Versión funcional, eliminará sus archivos:

find -type d -exec sh -c 'find "$0" -maxdepth 1 -mindepth 1 -type f -exec stat --printf "%s %n\0" \{\} + | sort -znr | tail -zn +2' {} \; | cut -zf 2- -d " " | xargs -0r rm

Explicación

Aquí está la versión de prueba dividida en varias líneas (aunque todavía es una línea para bash; tenga en cuenta que usoeste trucoa comentarios en línea):

find -type d -exec   `# Find all directories under (and including) the current one.` \
  sh -c '            `# In every directory separately...` \
    find "$0" -maxdepth 1 -mindepth 1 -type f -exec   `# ...find all files,...` \
      stat --printf "%s %n\0" \{\} + |   # ...get their sizes and names,...
    sort -znr |                          # ...sort by size...
    tail -zn +2'                        `# ...and discard the "biggest" entry.` \
    {} \
  \; |                                   # (All the directories have been processed).
cut -zf 2- -d " "  |                     # Then extract filenames...
xargs -0r ls -l                          # ...and ls them (rm in the working version).

Técnicas utilizadas, obstáculos superados:

  • A las herramientas que analizan cadenas se les indica que funcionen con cadenas terminadas en nulo:
    • stat --printf "…\0";
    • sort -z, tail -z, cut -z;
    • xargs -0 …;
    • find -print0(No es necesario en este ejemplo pero es muy común en general, por eso lo menciono de todos modos).
  • sh -c '…'es la forma de utilizar tuberías en el interior find -exec.
  • find -type d -exec sh -c 'find "{}" …se interrumpirá para el nombre del directorio que contenga "; find -type d -exec sh -c 'find "$0" … ' {} \;funciona bien.
  • {}en la finddeclaración interna se escapan ( \{\}) para evitar que la externa findlos sustituya.
  • cutpodría seguir inmediatamente tail, ejecutaría uno cutpor directorio. Colocarlo fuera del exterior findpermite cutrealizar todos los cortes a la vez.
  • La -ropción xargsevita que ls( rmen la versión funcional) se ejecute cuando no hay entrada en xargs.

información relacionada