Eliminar todos los archivos X más recientes en bash

¿Hay alguna manera sencilla, en un entorno UNIX bastante estándar con bash, de ejecutar un comando para eliminar todos los archivos X más los más recientes de un directorio?

Para dar un ejemplo más concreto, imagine que algún trabajo cron está escribiendo un archivo (por ejemplo, un archivo de registro o una copia de seguridad tar-ed) en un directorio cada hora. Me gustaría una forma de ejecutar otra tarea cron que elimine los archivos más antiguos en ese directorio hasta que haya menos de, digamos, 5.

Y para que quede claro, solo hay un archivo presente, nunca se debe eliminar.

Los problemas con las respuestas existentes:

  • incapacidad para manejar nombres de archivos con espacios incorporados o nuevas líneas.
    • en el caso de soluciones que invocan rm directamente en una sustitución de comando sin comillas ( rm `...` ), existe un riesgo adicional de globbing involuntario.
  • incapacidad de distinguir entre archivos y directorios (es decir, si los directorios se encontraban entre los 5 elementos de sistema de archivos modificados más recientemente, conservaría efectivamente menos de 5 archivos y la aplicación de rm a los directorios fallaría).

La respuesta de wnoise aborda estos problemas, pero la solución es específica de GNU (y bastante compleja).

Aquí hay una solución pragmática y compatible con POSIX que viene con una sola advertencia : no puede manejar nombres de archivos con líneas nuevas incorporadas, pero no considero que sea una preocupación real para la mayoría de la gente.

Para el registro, aquí está la explicación de por qué generalmente no es una buena idea analizar la salida de ls : http://mywiki.wooledge.org/ParsingLs

 ls -tp | grep -v '/$' | tail -n +6 | xargs -I {} rm -- {} 

Lo anterior es ineficiente , porque xargs tiene que invocar rm una vez para cada nombre de archivo.
Los xargs su plataforma pueden permitirle resolver este problema:

Si tiene xargs GNU , use -d '\n' , lo que hace que xargs considere cada línea de entrada como un argumento separado, pero pasa tantos argumentos como caben en una línea de comando a la vez :

 ls -tp | grep -v '/$' | tail -n +6 | xargs -d '\n' -r rm -- 

-r ( --no-run-if-empty ) asegura que no se invoca rm si no hay entrada.

Si tiene BSD xargs (incluso en OS X ), puede usar -0 para manejar NUL -separated input, después de traducir primero nuevas líneas a NUL ( 0x0 ) chars., Que también pasa (típicamente) todos los nombres de archivo a la vez (también funcionará con GNU xargs ):

 ls -tp | grep -v '/$' | tail -n +6 | tr '\n' '\0' | xargs -0 rm -- 

Explicación:

  • ls -tp imprime los nombres de los elementos del sistema de archivos ordenados por la forma en que se modificaron recientemente, en orden descendente (los últimos elementos modificados primero) ( -t ), con directorios impresos con un final / para marcarlos como tales ( -p ).
  • grep -v '/$' elimina los directorios de la lista resultante, omitiendo ( -v ) las líneas que tienen un / /$ / final.
    • Advertencia : como un enlace simbólico que apunta a un directorio no es técnicamente un directorio en sí mismo, tales enlaces simbólicos no se excluirán.
  • tail -n +6 omite las primeras 5 entradas de la lista, en efecto, devuelve todos menos los 5 últimos archivos modificados, si corresponde.
    Tenga en cuenta que para excluir N archivos, N+1 debe pasarse a tail -n + .
  • xargs -I {} rm -- {} (y sus variaciones) luego invoca en rm en todos estos archivos; si no hay ninguna coincidencia, xargs no hará nada.
    • xargs -I {} rm -- {} define el marcador de posición {} que representa cada línea de entrada como un todo , por lo que rm se invoca una vez para cada línea de entrada, pero con nombres de archivos con espacios integrados manejados correctamente.
    • -- en todos los casos se garantiza que cualquier nombre de archivo que empiece por - no se confunda con las opciones de rm .

Una variación del problema original, en caso de que los archivos coincidentes se procesen individualmente o se recopilen en una matriz de shell :

 # One by one, in a shell loop (POSIX-compliant): ls -tp | grep -v '/$' | tail -n +6 | while IFS= read -rf; do echo "$f"; done # One by one, but using a Bash process substitution (< (...), # so that the variables inside the `while` loop remain in scope: while IFS= read -rf; do echo "$f"; done < <(ls -tp | grep -v '/$' | tail -n +6) # Collecting the matches in a Bash *array*: IFS=$'\n' read -d '' -ra files < <(ls -tp | grep -v '/$' | tail -n +6) printf '%s\n' "${files[@]}" # print array elements 
 (ls -t|head -n 5;ls)|sort|uniq -u|xargs rm 

Esta versión admite nombres con espacios:

 (ls -t|head -n 5;ls)|sort|uniq -u|sed -e 's,.*,"&",g'|xargs rm 

Elimine todos menos 5 (o el número) de los archivos más recientes en un directorio.

 rm `ls -t | awk 'NR>5'` 

Variante más simple de la respuesta de thelsdj:

 ls -tr | head -n -5 | xargs rm --no-run-if-empty 

ls -tr muestra todos los archivos, el más antiguo primero (-t más nuevo primero, -r marcha atrás).

la cabeza -n muestra todas menos las 5 últimas líneas (es decir, los 5 archivos más nuevos).

xargs rm llama a rm para cada archivo seleccionado.

 find . -maxdepth 1 -type f -printf '%T@ %p\0' | sort -r -z -n | awk 'BEGIN { RS="\0"; ORS="\0"; FS="" } NR > 5 { sub("^[0-9]*(.[0-9]*)? ", ""); print }' | xargs -0 rm -f 

Requiere GNU find para -printf, y GNU sort para -z, y GNU awk para “\ 0”, y GNU xargs para -0, pero maneja los archivos con incrustados líneas nuevas o espacios.

Todas estas respuestas fallan cuando hay directorios en el directorio actual. Aquí hay algo que funciona:

 find . -maxdepth 1 -type f | xargs -x ls -t | awk 'NR>5' | xargs -L1 rm 

Esta:

  1. funciona cuando hay directorios en el directorio actual

  2. intenta eliminar cada archivo incluso si el anterior no se pudo eliminar (debido a permisos, etc.)

  3. falla cuando el número de archivos en el directorio actual es excesivo y xargs normalmente lo atornilla (el -x )

  4. no admite espacios en los nombres de archivo (¿quizás estás usando el sistema operativo equivocado?)

 ls -tQ | tail -n+4 | xargs rm 

Lista nombres de archivo por tiempo de modificación, citando cada nombre de archivo. Excluya los primeros 3 (3 más recientes). Eliminar el rest

EDITAR después de un comentario útil de mklement0 (¡gracias!): Argumento corregido -n + 3, y tenga en cuenta que esto no funcionará como se espera si los nombres de archivo contienen líneas nuevas y / o el directorio contiene subdirectorios.

Ignorar nuevas líneas es ignorar la seguridad y una buena encoding. wnoise tuvo la única buena respuesta. Aquí hay una variación de su que pone los nombres de archivo en una matriz $ x

 while IFS= read -rd ''; do x+=("${REPLY#* }"); done < <(find . -maxdepth 1 -printf '%T@ %p\0' | sort -r -z -n ) 

Si los nombres de archivo no tienen espacios, esto funcionará:

 ls -C1 -t| awk 'NR>5'|xargs rm 

Si los nombres de archivo tienen espacios, algo como

 ls -C1 -t | awk 'NR>5' | sed -e "s/^/rm '/" -e "s/$/'/" | sh 

Lógica básica:

  • obtener una lista de los archivos en orden cronológico, una columna
  • obtener todos menos los primeros 5 (n = 5 para este ejemplo)
  • primera versión: enviar esos a rm
  • segunda versión: gen una secuencia de comandos que los eliminará correctamente

Con zsh

Suponiendo que no le importan los directorios actuales y no tendrá más de 999 archivos (elija un número más grande si lo desea, o cree un bucle while).

 [ 6 -le `ls *(.)|wc -l` ] && rm *(.om[6,999]) 

En *(.om[6,999]) , el . significa archivos, el o significa ordenamiento ascendente, el m significa por fecha de modificación (ponga para el tiempo de acceso c para el cambio de inodo), el [6,999] elige un rango de archivo, por lo que no corresponde al 5 primero.

Me doy cuenta de que este es un hilo viejo, pero tal vez alguien se beneficiará de esto. Este comando encontrará los archivos en el directorio actual:

 for F in $(find . -maxdepth 1 -type f -name "*_srv_logs_*.tar.gz" -printf '%T@ %p\n' | sort -r -z -n | tail -n+5 | awk '{ print $2; }'); do rm $F; done 

Esto es un poco más sólido que algunas de las respuestas anteriores, ya que permite limitar su dominio de búsqueda a archivos que coincidan con expresiones. Primero, encuentre los archivos que coincidan con las condiciones que desee. Imprima esos archivos con las marcas de tiempo al lado de ellos.

 find . -maxdepth 1 -type f -name "*_srv_logs_*.tar.gz" -printf '%T@ %p\n' 

A continuación, ordenarlos por las marcas de tiempo:

 sort -r -z -n 

Luego, elimine los 4 archivos más recientes de la lista:

 tail -n+5 

Coge la segunda columna (el nombre de archivo, no la marca de tiempo):

 awk '{ print $2; }' 

Y luego envuelva todo eso en una statement for:

 for F in $(); do rm $F; done 

Este puede ser un comando más detallado, pero tuve mucha mejor suerte al poder apuntar a archivos condicionales y ejecutar comandos más complejos contra ellos.

encontré un cmd interesante en Sed-Onliners – Borre las últimas 3 líneas – y es perfecto para otra manera de despellejar al gato (no está bien) pero idea:

  #!/bin/bash # sed cmd chng #2 to value file wish to retain cd /opt/depot ls -1 MyMintFiles*.zip > BigList sed -n -e :a -e '1,2!{P;N;D;};N;ba' BigList > DeList for i in `cat DeList` do echo "Deleted $i" rm -f $i #echo "File(s) gonzo " #read junk done exit 0 
 leaveCount=5 fileCount=$(ls -1 *.log | wc -l) tailCount=$((fileCount - leaveCount)) # avoid negative tail argument [[ $tailCount < 0 ]] && tailCount=0 ls -t *.log | tail -$tailCount | xargs rm -f 

Hice esto en un guión de bash shell. Uso: keep NUM DIR donde NUM es la cantidad de archivos que desea conservar y DIR es el directorio que debe eliminarse.

 #!/bin/bash # Keep last N files by date. # Usage: keep NUMBER DIRECTORY echo "" if [ $# -lt 2 ]; then echo "Usage: $0 NUMFILES DIR" echo "Keep last N newest files." exit 1 fi if [ ! -e $2 ]; then echo "ERROR: directory '$1' does not exist" exit 1 fi if [ ! -d $2 ]; then echo "ERROR: '$1' is not a directory" exit 1 fi pushd $2 > /dev/null ls -tp | grep -v '/' | tail -n +"$1" | xargs -I {} rm -- {} popd > /dev/null echo "Done. Kept $1 most recent files in $2." ls $2|wc -l 

Elimina todos menos los 10 últimos archivos (los más recientes)

 ls -t1 | head -n $(echo $(ls -1 | wc -l) - 10 | bc) | xargs rm 

Si hay menos de 10 archivos, no se eliminará ningún archivo y tendrá: error head: contador de líneas ilegales – 0

Para contar archivos con bash

Ejecutar en Debian (supongo que es el mismo en otras distribuciones que obtengo: rm: no se puede eliminar el directorio `.. ‘

que es bastante molesto …

De todos modos, modifiqué lo anterior y también agregué grep al comando. En mi caso, tengo 6 archivos de respaldo en un directorio, por ejemplo, archivo1.tar archivo2.tar archivo3.tar, etc. y deseo eliminar solo el archivo más antiguo (elimine el primer archivo en mi caso)

La secuencia de comandos que ejecuté para eliminar el archivo más antiguo fue:

ls -C1 -t | archivo grep | awk ‘NR> 5’ | xargs rm

Esto (como el anterior) borra el primero de mis archivos, por ejemplo, archivo1.tar esto también deja estar con el archivo2 archivo3 archivo4 archivo5 y archivo6