Analizar la salida de nols
es confiable .
En su lugar, utilice find
para localizar los archivos y sort
ordenarlos por marca de tiempo. Por ejemplo:
while IFS= read -r -d $'\0' line ; do
file="${line#* }"
# do something with $file here
done < <(find . -maxdepth 1 -printf '%T@ %p\0' \
2>/dev/null | sort -z -n)
¿Qué está haciendo todo esto?
Primero, los find
comandos localizan todos los archivos y directorios en el directorio actual ( .
), pero no en subdirectorios del directorio actual ( -maxdepth 1
), luego imprime:
- Una marca de tiempo
- Un espacio
- La ruta relativa al archivo
- Un personaje NULL
La marca de tiempo es importante. El %T@
especificador de formato -printf
se desglosa en T
, que indica "Tiempo de última modificación" del archivo (mtime) y @
, que indica "Segundos desde 1970", incluidos los segundos fraccionarios.
El espacio es simplemente un delimitador arbitrario. La ruta completa al archivo es para que podamos consultarlo más tarde, y el carácter NULL es un terminador porque es un carácter ilegal en un nombre de archivo y, por lo tanto, nos permite saber con seguridad que llegamos al final de la ruta al archivo.
Lo he incluido 2>/dev/null
para que se excluyan los archivos a los que el usuario no tiene permiso de acceso, pero se suprimen los mensajes de error sobre su exclusión.
El resultado del find
comando es una lista de todos los directorios en el directorio actual. La lista se canaliza a la sort
que se indica que:
-z
Trate NULL como el carácter terminador de línea en lugar de nueva línea.
-n
Ordenar numéricamente
Como los segundos desde 1970 siempre suben, queremos el archivo cuya marca de tiempo sea el número más pequeño. El primer resultado sort
será la línea que contiene la marca de tiempo numerada más pequeña. Todo lo que queda es extraer el nombre del archivo.
Los resultados de la find
, sort
la tubería se pasa a través de la sustitución de proceso a while
donde se lee como si fuera un archivo en la entrada estándar. while
a su vez invoca read
para procesar la entrada.
En el contexto de read
establecemos la IFS
variable en nada, lo que significa que los espacios en blanco no se interpretarán inapropiadamente como un delimitador. read
se cuenta -r
, que desactiva la expansión de escape, y -d $'\0'
, lo que hace que el NULL delimitador de fin de línea, haciendo coincidir la salida de nuestra find
, sort
tubería.
La primera porción de datos, que representa la ruta de archivo más antigua precedida por su marca de tiempo y un espacio, se lee en la variable line
. A continuación, la sustitución de parámetros se usa con la expresión #*
, que simplemente reemplaza todos los caracteres desde el comienzo de la cadena hasta el primer espacio, incluido el espacio, sin nada. Esto elimina la marca de tiempo de modificación, dejando solo la ruta completa al archivo.
En este punto, el nombre del archivo está almacenado $file
y puede hacer lo que quiera con él. Cuando termine de hacer algo con $file
la while
instrucción, read
se ejecutará un bucle y el comando se ejecutará nuevamente, extrayendo el siguiente fragmento y el siguiente nombre de archivo.
¿No hay una manera más simple?
No. Las formas más simples son defectuosas.
Si usa ls -t
y canaliza hacia head
o tail
(o cualquier cosa ), romperá los archivos con nuevas líneas en los nombres de archivo. Si mv $(anything)
luego los archivos con espacios en blanco en el nombre causarán rotura. Si mv "$(anything)"
luego los archivos con líneas nuevas en el nombre causarán roturas. Si read
no -d $'\0'
, entonces usted va a romper en archivos con espacios en blanco en sus nombres.
Quizás en casos específicos usted sabe con certeza que una forma más simple es suficiente, pero nunca debe escribir suposiciones como esa en los scripts si puede evitar hacerlo.
Solución
#!/usr/bin/env bash
# move to the first argument
dest="$1"
# move from the second argument or .
source="${2-.}"
# move the file count in the third argument or 20
limit="${3-20}"
while IFS= read -r -d $'\0' line ; do
file="${line#* }"
echo mv "$file" "$dest"
let limit-=1
[[ $limit -le 0 ]] && break
done < <(find "$source" -maxdepth 1 -printf '%T@ %p\0' \
2>/dev/null | sort -z -n)
Llamar como:
move-oldest /mnt/backup/ /var/log/foo/ 20
Para mover los 20 archivos más antiguos de /var/log/foo/
a /mnt/backup/
.
Tenga en cuenta que estoy incluyendo archivos y directorios. Para archivos solo agregue -type f
a la find
invocación.
Gracias
Gracias a enzotib y Павел Танков por las mejoras a esta respuesta.