Das Parsen der Ausgabe von lsist nicht zuverlässig .
Verwenden Sie stattdessen, findum die Dateien zu lokalisieren und sortnach Zeitstempel zu ordnen. Beispielsweise:
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)
Was macht das alles?
Zuerst findsucht der Befehl alle Dateien und Verzeichnisse im aktuellen Verzeichnis ( .), jedoch nicht in den Unterverzeichnissen des aktuellen Verzeichnisses ( -maxdepth 1), und gibt dann Folgendes aus:
- Ein Zeitstempel
- Ein Leerzeichen
- Der relative Pfad zur Datei
- Ein NULL-Zeichen
Der Zeitstempel ist wichtig. Der %T@Formatbezeichner für -printfzerfällt in T"Letzte Änderungszeit" der Datei (mtime) und @"Sekunden seit 1970", einschließlich Sekundenbruchteile.
Das Leerzeichen ist lediglich ein beliebiger Begrenzer. Der vollständige Pfad zur Datei ist so, dass wir später darauf verweisen können, und das NULL-Zeichen ist ein Abschlusszeichen, da es sich um ein unzulässiges Zeichen in einem Dateinamen handelt. Auf diese Weise können wir sicher sein, dass wir das Ende des Pfads zur Datei erreicht haben Datei.
Ich habe eingeschlossen, 2>/dev/nulldamit Dateien, auf die der Benutzer keine Zugriffsberechtigung hat, ausgeschlossen werden, aber Fehlermeldungen darüber, dass sie ausgeschlossen werden, unterdrückt werden.
Das Ergebnis des findBefehls ist eine Liste aller Verzeichnisse im aktuellen Verzeichnis. Die Liste wird an Folgendes weitergeleitet sort:
-z Behandle NULL als Zeilenendezeichen anstelle von Newline.
-n Numerisch sortieren
Da die Sekunden seit 1970 immer höher sind, möchten wir die Datei, deren Zeitstempel die kleinste Zahl war. Das erste Ergebnis von sortist die Zeile mit dem kleinsten nummerierten Zeitstempel. Alles was bleibt ist, den Dateinamen zu extrahieren.
Die Ergebnisse der find, sortPipeline wird über geleitet Prozess Substitution zu , whilewo es gelesen wird , als ob es eine Datei auf stdin waren. whileRuft wiederum auf read, um die Eingabe zu verarbeiten.
Im Kontext von setzen readwir die IFSVariable auf nichts, was bedeutet, dass Leerzeichen nicht unangemessen als Trennzeichen interpretiert werden. readerzählt wird -r, welche escape Expansions deaktiviert, und -d $'\0', was die End-of-line - Trennzeichen NULL, Anpassen der Ausgabe aus unserer macht find, sortPipeline.
Der erste Datenblock, der den ältesten Dateipfad mit vorangestelltem Zeitstempel und Leerzeichen darstellt, wird in die Variable eingelesen line. Als nächstes wird die Parameterersetzung mit dem Ausdruck verwendet #*, der einfach alle Zeichen vom Anfang der Zeichenfolge bis zum ersten Leerzeichen, einschließlich des Leerzeichens, durch nichts ersetzt. Dadurch wird der Änderungszeitstempel entfernt, und es verbleibt nur der vollständige Pfad zur Datei.
Zu diesem Zeitpunkt ist der Dateiname in gespeichert $fileund Sie können alles tun, was Sie wollen. Wenn Sie mit $fileder whileAnweisung fertig sind, wird eine Schleife ausgeführt, und der readBefehl wird erneut ausgeführt, wobei der nächste Block und der nächste Dateiname extrahiert werden.
Gibt es nicht einen einfacheren Weg?
Einfachere Wege sind fehlerhaft.
Wenn Sie eine ls -tPipe zu headoder tail(oder etwas anderem ) verwenden, brechen Sie bei Dateien mit Zeilenumbrüchen in den Dateinamen ab. Wenn Sie mv $(anything)dann Dateien mit Leerzeichen im Namen verursachen Bruch. Wenn Sie mv "$(anything)"dann Dateien mit nachgestellten Zeilenumbrüchen im Namen erstellen, wird dies zu einem Bruch führen. Wenn Sie dies readnicht -d $'\0'tun, brechen Sie bei Dateien mit Leerzeichen im Namen ab.
Vielleicht wissen Sie in bestimmten Fällen, dass ein einfacherer Weg ausreicht, aber Sie sollten solche Annahmen niemals in Skripte schreiben, wenn Sie dies vermeiden können.
Lösung
#!/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)
Rufen Sie an wie:
move-oldest /mnt/backup/ /var/log/foo/ 20
Verschieben der ältesten 20 Dateien von /var/log/foo/nach /mnt/backup/.
Beachten Sie, dass ich Dateien und Verzeichnisse einbinde. Für Dateien nur -type fzum findAufruf hinzufügen .
Vielen Dank
Vielen Dank an Enzotib und Павел Танков für die Verbesserung dieser Antwort.