Analizar la salida de nols es confiable .
En su lugar, utilice findpara localizar los archivos y sortordenarlos 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 findcomandos 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 -printfse 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/nullpara 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 findcomando es una lista de todos los directorios en el directorio actual. La lista se canaliza a la sortque 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 sortserá 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, sortla tubería se pasa a través de la sustitución de proceso a whiledonde se lee como si fuera un archivo en la entrada estándar. whilea su vez invoca readpara procesar la entrada.
En el contexto de readestablecemos la IFSvariable en nada, lo que significa que los espacios en blanco no se interpretarán inapropiadamente como un delimitador. readse 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, sorttuberí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 $filey puede hacer lo que quiera con él. Cuando termine de hacer algo con $filela whileinstrucción, readse 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 -ty canaliza hacia heado 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 readno -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 fa la findinvocación.
Gracias
Gracias a enzotib y Павел Танков por las mejoras a esta respuesta.