L'analisi dell'output di nonls
è affidabile .
Invece, usa find
per localizzare i file e sort
per ordinarli in base al timestamp. Per esempio:
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)
Cosa sta facendo tutto questo?
Innanzitutto, i find
comandi individuano tutti i file e le directory nella directory corrente ( .
), ma non nelle sottodirectory della directory corrente ( -maxdepth 1
), quindi stampano:
- Un timestamp
- Uno spazio
- Il percorso relativo al file
- Un carattere NULL
Il timestamp è importante. L' %T@
identificatore di formato per si -printf
suddivide in T
, che indica "Ora ultima modifica" del file (mtime) e @
, che indica "Secondi dal 1970", compresi i secondi frazionari.
Lo spazio è semplicemente un delimitatore arbitrario. Il percorso completo del file è in modo che possiamo fare riferimento ad esso in un secondo momento, e il carattere NULL è un terminatore perché è un carattere illegale in un nome di file e quindi ci fa sapere con certezza che abbiamo raggiunto la fine del percorso per il file.
Ho incluso in 2>/dev/null
modo tale da escludere i file ai quali l'utente non ha il permesso di accedere, ma i messaggi di errore che li escludono vengono eliminati.
Il risultato del find
comando è un elenco di tutte le directory nella directory corrente. L'elenco viene reindirizzato a sort
cui viene richiesto di:
-z
Considera NULL come carattere di terminazione di riga anziché come nuova riga.
-n
Ordina numericamente
Poiché i secondi, dal 1970, salgono sempre, vogliamo il file il cui timestamp era il numero più piccolo. Il primo risultato da sort
sarà la riga contenente il timestamp numerato più piccolo. Non resta che estrarre il nome del file.
I risultati della pipeline find
, sort
vengono passati attraverso la sostituzione del processo nel punto in while
cui viene letto come se fosse un file su stdin. while
a sua volta invoca read
per elaborare l'input.
Nel contesto di read
impostare la IFS
variabile su nulla, il che significa che gli spazi bianchi non verranno interpretati in modo inappropriato come delimitatore. read
viene detto -r
, che disabilita l'espansione di fuga, e -d $'\0'
, il che rende il delimitatore NULL end-of-line, abbinando l'uscita dal nostro find
, sort
pipeline.
Il primo blocco di dati, che rappresenta il percorso del file più vecchio preceduto dal suo timestamp e da uno spazio, viene letto nella variabile line
. Successivamente, la sostituzione dei parametri viene utilizzata con l'espressione #*
, che sostituisce semplicemente tutti i caratteri dall'inizio della stringa fino al primo spazio, incluso lo spazio, con nulla. Questo elimina il timestamp di modifica, lasciando solo il percorso completo del file.
A questo punto il nome del file è archiviato $file
e puoi fare tutto ciò che ti piace. Quando hai finito di fare qualcosa con $file
l' while
istruzione, il ciclo read
verrà eseguito e il comando verrà eseguito di nuovo, estraendo il blocco successivo e il nome del file successivo.
Non c'è un modo più semplice?
No. I modi più semplici sono corretti.
Se si utilizza ls -t
e si reindirizza head
o tail
(o altro ) si interromperanno i file con le nuove righe nei nomi dei file. Se mv $(anything)
poi i file con spazi bianchi nel nome causeranno la rottura. Se mv "$(anything)"
poi file con nuove righe finali nel nome causeranno la rottura. Se read
senza -d $'\0'
Allora ti rompi il file con spazi nei loro nomi.
Forse in casi specifici sai per certo che è sufficiente un modo più semplice, ma non dovresti mai scrivere ipotesi del genere negli script se riesci a evitare di farlo.
Soluzione
#!/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)
Chiama come:
move-oldest /mnt/backup/ /var/log/foo/ 20
Per spostare i 20 file più vecchi da /var/log/foo/
a /mnt/backup/
.
Nota che sto includendo file e directory. Per i file solo aggiungere -type f
alla find
invocazione.
Grazie
Grazie a enzotib e Павел Танков per i miglioramenti a questa risposta.