ultima - Eliminar todos los archivos X más recientes en bash
sed linux (16)
¿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.
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.
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
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
Elimine todos menos 5 (o el número) de los archivos más recientes en un directorio.
rm `ls -t | awk ''NR>5''`
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
Ignorar nuevas líneas es ignorar la seguridad y una buena codificación. 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 )
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.
- en el caso de soluciones que invocan
- 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 excluirN
archivos,N+1
debe pasarse atail -n +
. -
xargs -I {} rm -- {}
(y sus variaciones) luego invoca enrm
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 querm
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 derm
.
-
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 -r f; 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 -r f; 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
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 declaración 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.
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
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:
funciona cuando hay directorios en el directorio actual
intenta eliminar cada archivo incluso si el anterior no se pudo eliminar (debido a permisos, etc.)
falla cuando el número de archivos en el directorio actual es excesivo y
xargs
normalmente lo atornilla (el-x
)no admite espacios en los nombres de archivo (¿quizás estás usando el sistema operativo equivocado?)
Variante más simple de la respuesta de thelsdj:
ls -tr | head -n -5 | xargs rm
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.
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
(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
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.
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
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 resto
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.