
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 3
deberí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
tree
enumera por directorio y luego por tamaño descendente.
awk
La primera línea de código omitetree
la primera línea de salida.olíneas con barras al final (es decir, directorios)awk
La segunda línea de código de 'crea un nombre de directorio a partir de la ruta completa (for
bucle), 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
ls
will 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 ls
los 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 ls
aquí a pesar de lo que acabo de decir. Esto se debe a que ls
la 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á ls
cuyo 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 ls
y 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 interiorfind -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 lafind
declaración interna se escapan (\{\}
) para evitar que la externafind
los sustituya.cut
podría seguir inmediatamentetail
, ejecutaría unocut
por directorio. Colocarlo fuera del exteriorfind
permitecut
realizar todos los cortes a la vez.- La
-r
opciónxargs
evita quels
(rm
en la versión funcional) se ejecute cuando no hay entrada enxargs
.