2008-08-25 23 views
109

¿Existe una manera simple, en un entorno UNIX bastante estándar con bash, de ejecutar un comando para eliminar todos los archivos X menos los más recientes de un directorio?Borrar todos menos los archivos X más recientes en bash

Para dar un ejemplo más concreto, imagínese que un trabajo cron escribe 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, por ejemplo, 5.

Y para que quede claro, solo hay un archivo presente, nunca debería ser eliminado

Respuesta

62

Los problemas con las respuestas existentes:

  • incapacidad para manejar nombres de archivo con espacios o saltos de línea embebidos.
    • en el caso de las soluciones que invocan rm directamente en una sustitución de orden sin comillas (rm `...`), hay un riesgo añadido de englobamiento no deseado.
  • incapacidad para distinguir entre los archivos y directorios (es decir, si ocurrieron directorios para estar entre los 5 elementos del sistema de archivos más recientemente modificados, que le retiene con eficacia menos que 5 archivos, y aplicando rm a directorios fallar).

wnoise's answer direcciones de estas cuestiones, pero la solución es GNU específico de (y bastante complejo).

he aquí una solución pragmática, compatible con POSIX que viene con solamente una advertencia: no puede manejar nombres de archivo con incrustados nuevas líneas - pero yo no considero que una preocupación en el mundo real para la mayoría de la gente.

Para el registro, aquí está la explicación de por qué no es generalmente una buena idea para analizar ls de salida: 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.
de xargs puede permitir que usted para solucionar este problema su plataforma:

Si tiene GNUxargs, utilice -d '\n', lo que hace xargs consideran cada línea de entrada un argumento separado, sin embargo, pasa a tantos argumentos como quepan en una línea de comandos a la vez :

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

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

Si tiene BSDxargs (incluyendo el OS X), se puede utilizar para manejar -0NUL de entrada Separado, después de traducir primero a las nuevas líneas NUL (0x0) caracteres., Que también pasa (normalmente) todos los ficheros a la vez (también trabajará con GNU xargs):

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

Explicación:

  • ls -tp grabados los nombres de los elementos del sistema de archivos ordenados por cuánto tiempo hace que se modificaron, en orden descendente (modificado más recientemente artículos primero) (-t), con directorios impresos con un trailing / para marcarlos como tal (-p).
  • grep -v '/$' elimina los directorios de la lista resultante, omitiendo (-v) líneas que tienen un / posterior (/$).
    • Advertencia: Desde un enlace simbólico que apunta a un directorio técnicamente no es en sí un directorio, se no ser excluidos dichos enlaces simbólicos.
  • tail -n +6 se salta los primeros 5 entradas en el perfil, en efecto, devolver todos los pero los 5 archivos más recientemente modificados, en su caso.
    Tenga en cuenta que para excluir los archivos N, N+1 debe pasarse a xargs -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 marcador de posición {} que representa cada línea de entrada como un todo, de modo rm es entonces invocado una vez por cada línea de entrada, pero con nombres de archivo con espacios incrustados se maneja correctamente.
    • -- en todos los casos se asegura de que los nombres de archivo que le suceden a comenzar con - no se confundan con las opciones por rm.

A variación en el problema original, en caso de que los archivos coincidentes necesitan ser procesados ​​individualmente o recogió en una matriz de cáscara:

# 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 
+2

Sin duda mejor que la mayoría de las otras respuestas aquí, así que estoy feliz de prestar mi apoyo, incluso en la medida en que considero ignorar el caso de nueva línea como algo que debe hacerse solo con precaución . –

+0

Excelente respuesta. ¿Qué sucede si quiero poner todos los archivos a eliminar en una matriz y procesarlos en un bucle for? (El tiempo de procesamiento no es un problema aquí.) – Bulrush

+1

Si '' ls' no está en el directorio actual, las rutas a los archivos contendrán '/', lo que significa que 'grep -v '/'' no coincidirá con nada . Creo que 'grep -v '/ $'' es lo que quieres excluir solo directorios. – waldol1

81

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

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

Conciso y relativamente legible. ¡Me gusta! –

+2

Necesitaba esto para considerar solo mis archivos. cambiar 'ls -t' a' ls -td * .bz2' –

+2

Utilicé esto para directorios cambiándolo a rm -rf 'ls -t | awk 'NR> 1'' (solo quería el más reciente). Gracias! – lohiaguitar91

83
(ls -t|head -n 5;ls)|sort|uniq -u|xargs rm 

Esta versión es compatible con nombres con espacios:

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

Este comando no manejará correctamente los archivos con espacios en los nombres. – tylerl

+0

Para corregir el uso anterior: '(ls -t | head -n 5; ls) | sort | uniq -u | sed-e 's,. *," & ", G' | xargs rm' – BroiSatse

+1

Éste falla si no hay archivos para eliminar – Mantas

4

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

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

Si los nombres de archivo tienen espacios , algo así 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 el orden del tiempo, una columna
  • obtener todos menos los primeros 5 (n = 5 para este ejemplo)
  • primera versión: enviar los a rm
  • segundos versión: gen una secuencia de comandos que les quite adecuadamente
+0

No olvide el truco 'while read' para tratar espacios: ' ls -C1 -t | awk 'NR> 5' | while read d; do rm -rvf "$ d"; done' – pinkeen

+1

@pinkeen, no es tan seguro como se indica allí. 'mientras que IFS = read -rd' sería un poco mejor - el' -r' evita que los literales de barra invertida siendo consumido por 'read', y' IFS = 'impide el recorte automático de los espacios en blanco finales. –

+3

BTW, si le preocupan los nombres de archivo hostiles , este es un enfoque ** extremadamente ** peligroso. Considere un archivo creado con 'touch $ 'hello \' $ (rm -rf ~) \ 'world''; las citas literales dentro del nombre de archivo contrarrestarían las citas literales que está agregando con 'sed', lo que da como resultado que se ejecute el código dentro del nombre de archivo. –

13

Todas estas respuestas fallar 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 

Este:

  1. funciona cuando hay directorios en el directorio actual

  2. trata de eliminar todos los archivos, incluso si el anterior no puede ser eliminado (debido a permisos, etc.)

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

  4. no está pensado para espacios en los nombres de archivo (tal vez usted está utilizando el sistema operativo mal?)

+4

¿Qué sucede si 'find' devuelve más nombres de archivo de los que se pueden pasar en una sola línea de comando a' ls -t'? (Sugerencia: obtiene múltiples ejecuciones de 'ls -t', cada una de las cuales solo se ordena individualmente, en lugar de tener un orden de clasificación globalmente correcto, por lo tanto, esta respuesta se rompe gravemente cuando se ejecuta con directorios suficientemente grandes). –

16
find . -maxdepth 1 -type f -printf '%[email protected] %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 encontrar para -printf , y GNU ordena para -z, y GNU awk para "\ 0", y GNU xargs para -0, pero maneja archivos con nuevas líneas o espacios incrustados.

+2

Si desea eliminar directorios, simplemente cambie el -f a -d y agregue un -r a la rm. encontrar. -maxdepth 1 -type d -printf '% T @% p \ 0' | ordenar -r -z -n | awk 'BEGIN {RS = "\ 0"; ORS = "\ 0"; FS = ""} NR> 5 {sub ("^ [0-9] * (. [0-9] *)?", ""); imprimir} '| xargs -0 rm -rf – alex

+1

De un vistazo, me sorprende la complejidad (o, en realidad, la necesidad) de la lógica 'awk'. ¿Me faltan algunos requisitos dentro de la pregunta del OP que lo hacen necesario? –

+0

@Charles Duffy: El sub() elimina la marca de tiempo, que es lo que se ordena. La marca de tiempo producida por "% T @" puede incluir una parte de fracción. La división en el espacio con FS rompe caminos con espacios integrados. Supongo que eliminar las primeras obras espaciales, pero es casi tan difícil de leer. Los separadores RS y ORS no se pueden establecer en la línea de comando, porque son NUL. – wnoise

8

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

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

Sugeriría que se borre 'IFS'; de lo contrario, correría el riesgo de perder espacios en blanco al final de los nombres de los archivos. Puede incluir eso en el comando de lectura: 'while IFS = read -rd ''; do' –

+0

@charlesduffy Hecho –

+1

por qué' "$ {RESPUESTA # *}" '? – msciwoj

-5

Correr en Debian (asuma su igual en otras distribuciones consigo: rm: no se puede eliminar el directorio `..'

que es bastante molesto ...

De todos modos, pellizqué 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 quiero 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 anteriormente) borra el primero de mis archivos, p. file1.tar esto también deja estar con fichero2 archivo3 file4 File5 y file6

2

Con zsh

Suponiendo que no se preocupan por los directorios actuales y que no tendrá más de 999 archivos (elegir un número más grande si usted quiere , o crea un ciclo while).

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

En *(.om[6,999]), la . significa archivos, el o significa orden de clasificación hasta, las m medios por fecha de modificación (ponen a por el tiempo de acceso o c para el cambio inodo), el [6,999] elige una serie de archivos, así que no rm el 5 primero.

+0

Intrigante, pero por mi vida no pude obtener el calificador de clasificación global ('om') para trabajar (cualquier clasificación que he probado no mostró ningún efecto, ni en OSX 10.11.2 (probado con zsh 5.0.8 y 5.1.1), ni en Ubuntu 14.04 (zsh 5.0. 2)) - ¿Qué es lo que me falta? En cuanto al punto final del rango: no hay necesidad de codificarlo, simplemente use '-1' para referirse a la última entrada y así incluir todas las filiales restantes es: '[6, -1]'. – mklement0

51

más simple variante de la respuesta de thelsdj:

ls -tr | head -n -5 | xargs rm 

ls -tr muestra todos los archivos, el más antiguo primero (-t nuevos primero, -R inversa).

head -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.

+13

Necesita agregar --no-run-if-empty a xargs para que no falle cuando hay menos de 5 archivos. – Tom

+1

-5 no funciona en bsd y macos :( –

+0

ls -1tr | head -n -5 | xargs rm <---------- necesita agregar un -1 al ls o ganó 'obtener una salida de lista para cabeza para trabajar correctamente contra –

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 
+1

'xargs' sin' -0' o al mínimo básico '-d $ '\ n'' no es confiable; observe cómo se comporta esto con un archivo con espacios o comillas en su nombre. –

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

lista los archivos por fecha de modificación, citando a cada nombre de archivo. Excluya los primeros 3 (3 más recientes). Eliminar el resto

EDITAR después del 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.

+0

La opción '' -Q'' no parece existir en mi máquina. –

+4

Hmm, la opción ha estado en GNU core utils durante ~ 20 años, pero no se menciona en las variantes de BSD. ¿Estás en un mac? – Mark

+0

Estoy de hecho. No creía que hubiera diferencias para este tipo de comandos realmente básicos entre sistemas actualizados. Gracias por tu respuesta ! –

0

Hice esto en una secuencia de comandos bash shell. Uso: keep NUM DIR donde NUM es la cantidad de archivos que se deben conservar y DIR es el directorio que se debe eliminar.

#!/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 
1

encontró cmd interesantes de SED-onliners - Eliminar 3 últimas líneas - fnd que es perfecto para otra forma a la piel del gato (bueno no), pero la 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 
1

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

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

esto es un poco más robusto que algunas de las respuestas anteriores ya que permite limitar su dominio de búsqueda de archivos que coinciden con las 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 '%[email protected] %p\n' 

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

sort -r -z -n 

A continuación, desprender los 4 archivos más recientes de la lista:

tail -n+5 

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

awk '{ print $2; }' 

An d luego envolver toda esa cosa en una sentencia for:

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

Esto puede ser un comando más detallado, pero tenía mucha mejor suerte de ser capaz de dirigirse a los archivos y ejecutar comandos condicionales más complejos contra ellos.

0

elimina todas las 10 últimas (mas recientes) Archivos

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

Si es menos de 10 archivos ningún archivo se elimina y se tendrán: cabeza de error: número de líneas ilegales - 0

To count files with bash

Cuestiones relacionadas