Los problemas con las respuestas existentes:
- incapacidad para manejar nombres de archivos con espacios incrustados 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 bloqueo involuntario.
- incapacidad para distinguir entre archivos y directorios (es decir, si los directorios se encontraran entre los 5 elementos del sistema de archivos modificados más recientemente, efectivamente retendría menos de 5 archivos, y la aplicación
rm
a los directorios fallará).
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 archivo con líneas nuevas incrustadas , pero no considero que sea una preocupación del mundo real para la mayoría de las personas.
Para el registro, aquí está la explicación de por qué generalmente no es una buena idea analizar la ls
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.
Su plataforma xargs
puede permitirle resolver este problema:
Si tiene GNU xargs
, use -d '\n'
, lo que hace que xargs
cada línea de entrada sea un argumento separado, pero pasa tantos argumentos como quepan 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 rm
no se invoque si no hay entrada.
Si tiene BSD xargs
(incluido en macOS ), puede usarlo -0
para manejar la NUL
entrada separada, después de traducir primero las nuevas líneas a NUL
( 0x0
) caracteres, 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 última vez que se modificaron, en orden descendente (los elementos modificados más recientemente primero) ( -t
), con directorios impresos con un final /
para marcarlos como tales ( -p
).
grep -v '/$'
luego elimina los directorios de la lista resultante, omitiendo las -v
líneas ( ) que tienen un final /
( /$
).
- Advertencia : dado que un enlace simbólico que apunta a un directorio técnicamente no es en sí un directorio, dichos enlaces simbólicos no se excluirán.
tail -n +6
se salta las primeras 5 entradas en el perfil, en efecto, volviendo todo , pero los 5 archivos más recientemente modificados, en su caso.
Tenga en cuenta que para excluir N
archivos, N+1
debe pasarse a tail -n +
.
xargs -I {} rm -- {}
(y sus variaciones) luego se invoca rm
en todos estos archivos; Si no hay coincidencias, 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 archivo con espacios incrustados manejados correctamente.
--
en todos los casos asegura que cualquier nombre de archivo con el que empiece -
no se confunda con las opciones de rm
.
Una variación del problema original, en caso de que los archivos coincidentes deban procesarse individualmente o recopilarse 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
ls
está en el directorio actual, las rutas a los archivos contendrán '/', lo que significa quegrep -v '/'
no coincidirán con nada. Creo quegrep -v '/$'
es lo que desea excluir solo directorios.grep
comando sea conceptualmente más claro. Sin embargo, tenga en cuenta que el problema que describe no habría surgido con una sola ruta de directorio; por ejemplo,ls -p /private/var
solo imprimiría simples nombres de archivo. Solo si pasa varios argumentos de archivo (generalmente a través de un globo) vería rutas reales en la salida; por ejemplo,ls -p /private/var/*
(y también verá el contenido de los subdirectorios coincidentes, a menos que también lo haya incluido-d
).Elimine todos menos 5 (o cualquier número) de los archivos más recientes en un directorio.
fuente
ls -t
als -td *.bz2
ls -t | awk 'NR>1'
(solo quería el más reciente). ¡Gracias!ls -t | awk 'NR>5' | xargs rm -f
si prefiere tuberías y necesita suprimir el error si no hay nada que eliminar.touch 'hello * world'
, esto eliminaría absolutamente todo en el directorio actual .Esta versión admite nombres con espacios:
fuente
(ls -t|head -n 5;ls)
es un grupo de comandos . Imprime los 5 archivos más recientes dos veces.sort
pone líneas idénticas juntas.uniq -u
elimina los duplicados, de modo que quedan todos menos los 5 archivos más recientes.xargs rm
llamarm
a cada uno de ellos.--no-run-if-empty
axargs
como en(ls -t|head -n 5;ls)|sort|uniq -u|xargs --no-run-if-empty rm
por favor actualice la respuesta.touch 'foo " bar'
eliminará todo el resto del comando.xargs -d $'\n'
que a cotizaciones inyectar en su contenido, aunque NUL-delimita el flujo de entrada (que requiere el uso de algo que no seals
a realmente hacer a la derecha) es la opción ideal.Variante más simple de la respuesta de thelsdj:
ls -tr muestra todos los archivos, el más antiguo primero (-t el más nuevo primero, -r al revés).
head -n -5 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.
fuente
-1
es predeterminado cuando la salida es a una tubería, por lo que no es obligatorio aquí. Esto tiene problemas mucho mayores, relacionados con el comportamiento predeterminadoxargs
al analizar nombres con espacios, comillas, etc.--no-run-if-empty
no se reconoce en mi shell. Estoy usando Cmder en Windows.-0
opción si los nombres de archivo pueden contener espacios en blanco. Sin embargo, aún no lo he probado. fuenteRequiere GNU find para -printf, y GNU sort para -z, y GNU awk para "\ 0", y GNU xargs para -0, pero maneja archivos con líneas o espacios incrustados.
fuente
awk
lógica. ¿Me faltan algunos requisitos dentro de la pregunta del OP que lo hacen necesario?while read -r -d ' '; IFS= -r -d ''; do ...
bucle de shell : la primera lectura termina en el espacio, mientras que la segunda pasa al NUL.sed -z -e 's/[^ ]* //; 1,5d'
es el más claro. (o tal vezsed -n -z -e 's/[^ ]* //; 6,$p'
.Todas estas respuestas fallan cuando hay directorios en el directorio actual. Aquí hay algo que funciona:
Esta:
funciona cuando hay directorios en el directorio actual
intenta eliminar cada archivo incluso si no se pudo eliminar el anterior (debido a permisos, etc.)
falla a salvo cuando el número de archivos en el directorio actual es excesivo y
xargs
normalmente lo atornillaría (el-x
)no atiende espacios en los nombres de archivo (¿tal vez está utilizando el sistema operativo incorrecto?)
fuente
find
devuelve más nombres de archivos de los que se pueden pasar en una sola línea de comandols -t
? (Sugerencia: obtienes varias ejecucionesls -t
, cada una de las cuales está ordenada individualmente, en lugar de tener un orden de clasificación globalmente correcto; por lo tanto, esta respuesta se rompe mal cuando se ejecuta con directorios suficientemente grandes).Enumere los nombres de archivo por hora de modificación, citando cada nombre de archivo. Excluir los primeros 3 (3 más recientes). Eliminar el resto.
EDITE después de un comentario útil de mklement0 (¡gracias!): Corregido el argumento -n + 3, y tenga en cuenta que esto no funcionará como se esperaba si los nombres de archivo contienen nuevas líneas y / o el directorio contiene subdirectorios.
fuente
-Q
opción no parece existir en mi máquina.-Q
. Sí,-Q
es una extensión GNU (aquí está la especificación POSIXls
). Una pequeña advertencia (rara vez es un problema en la práctica):-Q
codifica nuevas líneas incrustadas en nombres de archivos como literales\n
, lorm
que no reconocerá. Para excluir los primeros 3 , elxargs
argumento debe+4
. Finalmente, una advertencia que también se aplica a la mayoría de las otras respuestas: su comando solo funcionará según lo previsto si no hay subdirectorios en el directorio actual.--no-run-if-empty
opción:ls -tQ | tail -n+4 | xargs --no-run-if-empty rm
Ignorar las nuevas líneas es ignorar la seguridad y la buena codificación. wnoise tuvo la única buena respuesta. Aquí hay una variación de él que pone los nombres de archivo en una matriz $ x
fuente
IFS
, de lo contrario correría el riesgo de perder el espacio en blanco al final de los nombres de archivo. Puede abarcar eso con el comando de lectura:while IFS= read -rd ''; do
"${REPLY#* }"
?Si los nombres de archivo no tienen espacios, esto funcionará:
Si los nombres de archivo tienen espacios, algo así como
Lógica básica:
fuente
while read
truco para lidiar con espacios:ls -C1 -t | awk 'NR>5' | while read d ; do rm -rvf "$d" ; done
while IFS= read -r d
sería un poco mejor:-r
evita que los literales de barra invertida sean consumidosread
yIFS=
evita el recorte automático del espacio en blanco al final.touch $'hello \'$(rm -rf ~)\' world'
; las comillas literales dentro del nombre del archivo contrarrestarían las comillas literales con las que está agregandosed
, dando como resultado que se ejecute el código dentro del nombre del archivo.| sh
formulario, que es el que tiene la vulnerabilidad de inyección de shell).Con zsh
Asumiendo que no le interesan los directorios actuales y que no tendrá más de 999 archivos (elija un número mayor si lo desea, o cree un ciclo while).
En
*(.om[6,999])
los.
archivos de medios, elo
orden de clasificación de los medios, losm
medios por fecha de modificación (puestoa
para el tiempo de acceso oc
para el cambio de inodo),[6,999]
elige un rango de archivo, por lo que no rm los 5 primero.fuente
om
) (cualquier clasificación que he intentado 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é me estoy perdiendo? En cuanto a la gama de punto final: no hay necesidad de codificar, basta con utilizar-1
para referirse a la última entrada y por lo tanto incluye todos los archivos restantes:[6,-1]
.Me doy cuenta de que este es un hilo viejo, pero tal vez alguien se beneficie de esto. Este comando encontrará archivos en el directorio actual:
Esto es un poco más robusto que algunas de las respuestas anteriores, ya que permite limitar su dominio de búsqueda a archivos que coincidan con expresiones. Primero, encuentre archivos que coincidan con las condiciones que desee. Imprima esos archivos con las marcas de tiempo junto a ellos.
A continuación, ordénelos por las marcas de tiempo:
Luego, elimine los 4 archivos más recientes de la lista:
Tome la segunda columna (el nombre del archivo, no la marca de tiempo):
Y luego envuelva todo en una declaración for:
Este puede ser un comando más detallado, pero tuve mucha mejor suerte al poder dirigir archivos condicionales y ejecutar comandos más complejos contra ellos.
fuente
encontró cmd interesante en Sed-Onliners - Elimina las últimas 3 líneas - encuentra perfecto para otra forma de pelar al gato (está bien, no) pero idea:
fuente
Elimina todos menos los 10 últimos archivos (la mayoría recientes)
Si hay menos de 10 archivos, no se eliminará ningún archivo y tendrá: cabeza de error: recuento de líneas ilegales - 0
Para contar archivos con bash
fuente
Necesitaba una solución elegante para el busybox (enrutador), todas las soluciones de xargs o array me resultaron inútiles; no hay tal comando disponible allí. find and mtime no es la respuesta adecuada, ya que estamos hablando de 10 elementos y no necesariamente de 10 días. La respuesta de Espo fue la más corta, más limpia y probablemente la más irreversible.
Los errores con espacios y cuando no se van a eliminar archivos se resuelven simplemente de la manera estándar:
Versión un poco más educativa: podemos hacerlo todo si usamos awk de manera diferente. Normalmente, uso este método para pasar (devolver) variables del awk al sh. Como leemos todo el tiempo que no se puede hacer, ruego diferir: aquí está el método.
Ejemplo para archivos .tar sin problemas con respecto a los espacios en el nombre del archivo. Para probar, reemplace "rm" con el "ls".
Explicación:
ls -td *.tar
enumera todos los archivos .tar ordenados por hora. Para aplicar a todos los archivos en la carpeta actual, elimine la parte "d * .tar"awk 'NR>7...
salta las primeras 7 líneasprint "rm \"" $0 "\""
construye una línea: rm "nombre de archivo"eval
lo ejecutaComo estamos usando
rm
, ¡no usaría el comando anterior en un script! El uso más sabio es:En el caso de usar el
ls -t
comando no hará ningún daño en ejemplos tan tontos como:touch 'foo " bar'
ytouch 'hello * world'
. ¡No es que alguna vez creamos archivos con tales nombres en la vida real!Nota al margen. Si quisiéramos pasar una variable al sh de esta manera, simplemente modificaríamos la impresión (forma simple, sin espacios tolerados):
para establecer la variable
VarName
al valor de$1
. Se pueden crear múltiples variables de una vez. Esto seVarName
convierte en una variable sh normal y se puede usar normalmente en un script o shell después. Entonces, para crear variables con awk y devolverlas al shell:fuente
fuente
xargs
sin-0
o como mínimo-d $'\n'
es poco confiable; observe cómo se comporta esto con un archivo con espacios o comillas en su nombre.Hice esto en un script de shell bash. Uso:
keep NUM DIR
donde NUM es el número de archivos que se deben mantener y DIR es el directorio que se desea eliminar.fuente