Wie finde ich die älteste Datei in einem Verzeichnisbaum?

72

Ich suche einen Shell-Einzeiler, um die älteste Datei in einem Verzeichnisbaum zu finden.

Marius Gedminas
quelle

Antworten:

72

Dies funktioniert (aktualisiert, um den Vorschlag von Daniel Andersson zu berücksichtigen):

find -type f -printf '%T+ %p\n' | sort | head -n 1
Marius Gedminas
quelle
8
Weniger tippen:find -type f -printf '%T+ %p\n' | sort | head -1
Daniel Andersson
1
Ich bekomme leeren Raum, weil meine erste Zeile findleer ist, weil der Dateiname eine neue Zeile enthält.
林果 皞
1
Kann ich fragen, ob das Erstellungs- oder Änderungsdatum verwendet wird?
MrMesees
1
Linux speichert das Erstellungsdatum der Datei nirgendwo [*]. Dies verwendet das Änderungsdatum. [*] das ist eigentlich nicht wahr; ext4 speichert das Erstellungsdatum des Inodes, es wird jedoch nicht über Systemaufrufe
angezeigt,
11

Dies ist etwas portabler und weil es nicht auf der GNU- findErweiterung basiert -printf, funktioniert es auch unter BSD / OS X:

find . -type f -print0 | xargs -0 ls -ltr | head -n 1

Der einzige Nachteil hier ist, dass es etwas auf die Größe von beschränkt ist ARG_MAX(was für die meisten neueren Kernel irrelevant sein sollte ). Wenn also mehr als getconf ARG_MAXZeichen zurückgegeben werden (262.144 auf meinem System), erhalten Sie nicht das richtige Ergebnis. Es ist auch nicht POSIX-konform, weil -print0und xargs -0nicht.

Weitere Lösungen für dieses Problem finden Sie hier: Wie kann ich die neueste (neueste, älteste, älteste) Datei in einem Verzeichnis finden? - Gregs Wiki

slhck
quelle
Dies funktioniert auch, aber es wird auch ein xargs: ls: terminated by signal 13Fehler als Nebeneffekt ausgegeben. Ich vermute, das ist SIGPIPE. Ich habe keine Ahnung, warum ich keinen ähnlichen Fehler erhalte, wenn ich die Ausgabe von sort in meine Lösung pipe.
Marius Gedminas
Ihre Version ist auch einfacher aus dem Speicher einzugeben. :-)
Marius Gedminas
Ja, das ist eine kaputte Pfeife. Ich verstehe das nicht mit GNU- und BSD-Versionen all dieser Befehle, aber es ist der headBefehl, der beendet wird, sobald er eine Zeile gelesen hat und somit die Pipe "durchbricht", denke ich. Sie erhalten den Fehler nicht, weil er sortsich nicht zu beschweren lsscheint, im anderen Fall jedoch.
Slhck
4
Dies bricht ab, wenn es so viele Dateinamen gibt, xargsdie lsmehr als einmal aufgerufen werden müssen. In diesem Fall werden die sortierten Ausgaben dieser mehreren Aufrufe verkettet, wenn sie zusammengeführt werden sollen.
Nicole Hamilton
2
Ich denke, das ist schlimmer als das Posten eines Skripts, bei dem angenommen wird, dass Dateinamen keine Leerzeichen enthalten. In den meisten Fällen funktionieren diese, da die Dateinamen keine Leerzeichen enthalten. Und wenn sie fehlschlagen, erhalten Sie eine Fehlermeldung. Es ist jedoch unwahrscheinlich, dass dies in realen Fällen funktioniert, und das Scheitern wird unentdeckt bleiben. An jedem Verzeichnisbaum groß genug , dass man nicht einfach kann lses und die älteste Datei, Ihre Lösung Augapfel wahrscheinlich wird die Kommandozeile Längenbegrenzung überrannt, was lsmehrfach aufgerufen werden. Sie werden die falsche Antwort bekommen, aber Sie werden es nie erfahren.
Nicole Hamilton
11

Die folgenden Befehle funktionieren garantiert mit jeder Art von seltsamen Dateinamen:

find -type f -printf "%T+ %p\0" | sort -z | grep -zom 1 ".*" | cat

find -type f -printf "%T@ %T+ %p\0" | \
    sort -nz | grep -zom 1 ".*" | sed 's/[^ ]* //'

stat -c "%y %n" "$(find -type f -printf "%T@ %p\0" | \
    sort -nz | grep -zom 1 ".*" | sed 's/[^ ]* //')"

Die Verwendung eines Nullbytes ( \0) anstelle eines Zeilenvorschubzeichens ( \n) stellt sicher, dass die Ausgabe von find weiterhin verständlich ist, falls einer der Dateinamen ein Zeilenvorschubzeichen enthält.

Der -zSchalter bewirkt, dass sowohl sort als auch grep nur Nullbytes als Zeilenendezeichen interpretieren. Da es keinen solchen Schalter für den Kopf gibt, verwenden wir grep -m 1stattdessen (nur ein Vorkommen).

Die Befehle sind nach Ausführungszeit geordnet (gemessen auf meinem Computer).

  • Der erste Befehl ist der langsamste, da er die Zeit jeder Datei zuerst in ein für Menschen lesbares Format konvertieren und dann diese Zeichenfolgen sortieren muss. Durch das Weiterleiten an cat wird das Einfärben der Ausgabe vermieden.

  • Der zweite Befehl ist etwas schneller. Während die Datumskonvertierung noch ausgeführt wird, ist die numerische Sortierung ( sort -n) der seit der Unix-Epoche verstrichenen Sekunden etwas schneller. sed löscht die Sekunden seit der Unix-Epoche.

  • Der letzte Befehl führt überhaupt keine Konvertierung durch und sollte deutlich schneller sein als die ersten beiden. Der Befehl find selbst zeigt nicht die Uhrzeit der ältesten Datei an, daher ist stat erforderlich.

Verwandte Manpages: find - grep - sed - sort - stat

Dennis
quelle
5

Obwohl die akzeptierte Antwort und andere hier die Aufgabe erfüllen, werden bei einem sehr großen Baum alle Dateien sortiert.

Besser wäre es, wenn wir sie einfach auflisten und den Überblick über die ältesten behalten könnten, ohne sie zu sortieren.

Deshalb habe ich mir diese alternative Lösung ausgedacht:

ls -lRU $PWD/* | awk 'BEGIN {cont=0; oldd=strftime("%Y%m%d"); } { gsub(/-/,"",$6); if (substr($1,0,1)=="/") { pat=substr($1,0,length($0)-1)"/"; }; if( $6 != "") {if ( $6 < oldd ) { oldd=$6; oldf=pat$8; }; print $6, pat$8; count++;}} END { print "Oldest date: ", oldd, "\nFile:", oldf, "\nTotal compared: ", count}'

Ich hoffe, es könnte hilfreich sein, auch wenn die Frage ein bisschen alt ist.


Bearbeiten 1: Diese Änderungen ermöglichen das Parsen von Dateien und Verzeichnissen mit Leerzeichen. Es ist schnell genug, um es im Stammverzeichnis auszugeben /und die älteste Datei zu finden, die es je gab.

ls -lRU --time-style=long-iso "$PWD"/* | awk 'BEGIN {cont=0; oldd=strftime("%Y%m%d"); } { gsub(/-/,"",$6); if (substr($0,0,1)=="/") { pat=substr($0,0,length($0)-1)"/"; $6="" }; if( $6 ~ /^[0-9]+$/) {if ( $6 < oldd ) { oldd=$6; oldf=$8; for(i=9; i<=NF; i++) oldf=oldf $i; oldf=pat oldf; }; count++;}} END { print "Oldest date: ", oldd, "\nFile:", oldf, "\nTotal compared: ", count}'

Befehl erklärt:

  • ls -lRU --time-style = long-iso "$ PWD" / * listet alle Dateien (*) im Langformat (l) rekursiv (R) auf, ohne dass die Sortierung (U) schnell sein muss, und leitet sie an awk weiter
  • Awk, dann BEGINNEN, indem Sie den Zähler auf Null setzen (optional für diese Frage) und das älteste Datum auf Heute setzen. Formatieren Sie YearMonthDay.
  • Die Hauptschleife zuerst
    • Ergreift das 6. Feld, das Datum, formatiert Jahr-Monat-Tag und ändert es in YearMonthDay (wenn Ihr ls nicht auf diese Weise ausgibt, müssen Sie es möglicherweise fein abstimmen).
    • Bei Verwendung von rekursiv werden für alle Verzeichnisse Kopfzeilen in Form von / directory / here: angezeigt. Nehmen Sie diese Zeile in die Variable pat auf. (Ersetzen des letzten ":" durch ein "/"). Und setzt $ 6 auf nichts, um zu vermeiden, dass die Kopfzeile als gültige Dateizeile verwendet wird.
    • Wenn das Feld $ 6 eine gültige Nummer hat, ist es ein Datum. Vergleichen Sie es mit dem alten Datum oldd.
    • Ist es älter Speichern Sie dann die neuen Werte für das alte Datum oldd und den alten Dateinamen oldf. Übrigens ist oldf nicht nur das 8. Feld, sondern vom 8. bis zum Ende. Das ist der Grund, warum eine Schleife vom 8. zum NF (Ende) verkettet werden soll.
    • Zähle die Fortschritte um eins
    • ENDE durch Drucken des Ergebnisses

Laufen es:

~ $ time ls -lRU "$ PWD" / * | awk etc.

Ältestes Datum: 19691231

Datei: /home/.../.../backupold/.../EXAMPLES/how-to-program.txt

Insgesamt verglichen: 111438

echte 0m1.135s

Benutzer 0m0.872s

sys 0m0.760s


EDIT 2: Gleiches Konzept, bessere Lösung mit findBlick auf die Zugriffszeit ( %Tmit der ersten printffür Änderungszeit oder stattdessen %Cfür Statusänderung ).

find . -wholename "*" -type f -printf "%AY%Am%Ad %h/%f\n" | awk 'BEGIN {cont=0; oldd=strftime("%Y%m%d"); } { if ($1 < oldd) { oldd=$1; oldf=$2; for(i=3; i<=NF; i++) oldf=oldf " " $i; }; count++; } END { print "Oldest date: ", oldd, "\nFile:", oldf, "\nTotal compared: ", count}'

BEARBEITEN 3: Der folgende Befehl verwendet die Änderungszeit und gibt auch den inkrementellen Fortschritt aus, wenn ältere und ältere Dateien gefunden werden. Dies ist hilfreich, wenn Sie falsche Zeitstempel haben (wie zum Beispiel 1970-01-01):

find . -wholename "*" -type f -printf "%TY%Tm%Td %h/%f\n" | awk 'BEGIN {cont=0; oldd=strftime("%Y%m%d"); } { if ($1 < oldd) { oldd=$1; oldf=$2; for(i=3; i<=NF; i++) oldf=oldf " " $i; print oldd " " oldf; }; count++; } END { print "Oldest date: ", oldd, "\nFile:", oldf, "\nTotal compared: ", count}'
Dr. Beco
quelle
Es muss immer noch gezwitschert werden, um Dateien mit Leerzeichen zu akzeptieren. Das mache ich gleich.
Dr. Beco
Ich denke, ls für Dateien mit Leerzeichen zu analysieren, ist keine gute Idee. Vielleicht mit find.
Dr. Beco
Führen Sie es einfach im gesamten Baum "/" aus. Zeitaufwand: Insgesamt verglichen: 585744 echte 2M14.017s Benutzer 0M8.181s Sys 0M8.473s
Dr Beco
Die Verwendung von lsist für Skripte ungeeignet, da die Ausgabe nicht für Computer bestimmt ist. Die Ausgabeformatierung variiert je nach Implementierung. Wie Sie bereits sagten, findist es gut für Skripte, aber es kann auch sinnvoll sein, diese Informationen hinzuzufügen, bevor Sie über lsLösungen sprechen .
Sampo Sarrala
4

Bitte benutzen Sie ls - die Manpage zeigt Ihnen, wie Sie das Verzeichnis bestellen können.

ls -clt | head -n 2

Das -n 2 ist so, dass Sie nicht die "Summe" in der Ausgabe erhalten. Wenn Sie nur den Namen der Datei wollen.

ls -t | head -n 1

Und wenn Sie die Liste in der normalen Reihenfolge benötigen (immer die neueste Datei)

ls -tr | head -n 1

Viel einfacher als die Verwendung von find, viel schneller und robuster - Sie müssen sich keine Gedanken über Dateibenennungsformate machen. Es sollte auch auf fast allen Systemen funktionieren.

user1363990
quelle
6
Dies funktioniert nur, wenn sich die Dateien in einem einzelnen Verzeichnis befinden, während es sich bei meiner Frage um einen Verzeichnisbaum handelte.
Marius Gedminas
2
find ! -type d -printf "%T@ %p\n" | sort -n | head -n1
Okki
quelle
Dies funktioniert nicht richtig, wenn Dateien vorhanden sind, die älter als der 9. September 2001 sind (1000000000 Sekunden seit der Unix-Epoche). Verwenden Sie, um die numerische Sortierung zu aktivieren sort -n.
Dennis
Das hilft mir, die Datei zu finden, aber es ist schwer zu erkennen, wie alt sie ist, ohne einen zweiten Befehl
auszuführen
0

Es scheint, dass die meisten Leute unter "ältesten" angenommen haben, dass Sie "älteste Änderungszeit" gemeint haben. Das ist wahrscheinlich nach der strengsten Interpretation von "ältesten" korrigiert, aber falls Sie die mit der ältesten Zugriffszeit wollten , würde ich die beste Antwort folgendermaßen modifizieren:

find -type f -printf '%A+ %p\n' | sort | head -n 1

Beachten Sie die %A+.

PenguinLust
quelle
-1
set $(find /search/dirname -type f -printf '%T+ %h/%f\n' | sort | head -n 1) && echo $2
  • find ./search/dirname -type f -printf '%T+ %h/%f\n' druckt Datums- und Dateinamen in zwei Spalten.
  • sort | head -n1 behält die Zeile bei, die der ältesten Datei entspricht.
  • echo $2 zeigt die zweite Spalte an, dh den Dateinamen.
Dima
quelle
1
Willkommen bei Super User! Während dies die Frage beantworten mag, wäre es eine bessere Antwort, wenn Sie eine Erklärung dafür liefern könnten .
DavidPostill
1
Beachten Sie, dass mehrere Personen um eine Erklärung Ihrer vorherigen (identischen) gelöschten Antwort gebeten haben.
DavidPostill
Was ist schwer zu beantworten? find ./search/dirname -type f -printf '% T +% h /% f \ n' | sortieren | head -n 1 Zeigt zwei Spalten als Uhrzeit und Pfad der Datei an. Es ist notwendig, die erste Spalte zu entfernen. Verwenden von Set und Echo $ 2
Dima
1
Sie sollten Erklärungen bereitstellen, anstatt nur eine Befehlszeile einzufügen, wie dies von mehreren anderen Benutzern angefordert wurde.
Ob1lan
1
Wie ist das anders als die akzeptierte Antwort?
Ramhound