Comment trouver le fichier le plus ancien dans une arborescence de répertoires

72

Je cherche un shell one-liner pour trouver le fichier le plus ancien dans une arborescence de répertoires.

Marius Gedminas
la source

Réponses:

72

Cela fonctionne (mis à jour pour intégrer la suggestion de Daniel Andersson):

find -type f -printf '%T+ %p\n' | sort | head -n 1
Marius Gedminas
la source
8
Moins de frappe:find -type f -printf '%T+ %p\n' | sort | head -1
Daniel Andersson
1
Je reçois un espace vide parce que ma première ligne findest vide car mon nom de fichier contient newline.
林果皞
1
Puis-je demander si cela utilise la date de création ou de modification?
MrMesees
1
Linux ne stocke la date de création du fichier nulle part [*]. Ceci utilise la date de modification. [*] ce n'est en réalité pas vrai; ext4 stocke la date de création de l'inode, mais il n'est pas exposé via aucun appel système et vous devez utiliser le fichier debugfs pour le voir.)
Marius Gedminas Le
11

Celui-ci est un peu plus portable et, comme il ne repose pas sur l' findextension GNU -printf, il fonctionne donc aussi sous BSD / OS X:

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

Le seul inconvénient ici est qu'il est quelque peu limité à la taille de ARG_MAX(ce qui ne devrait pas être pertinent pour la plupart des nouveaux noyaux). Donc, s'il y a plus de getconf ARG_MAXcaractères renvoyés (262 144 sur mon système), le résultat n'est pas correct. Ce n'est pas non plus compatible POSIX car -print0et xargs -0ne l'est pas.

Quelques solutions supplémentaires à ce problème sont décrites ci-dessous: Comment trouver le dernier fichier (le plus récent, le plus ancien, le plus ancien) dans un répertoire? - Wiki de Greg

slhck
la source
Cela fonctionne aussi, mais une xargs: ls: terminated by signal 13erreur est également émise . Je suppose que c'est SIGPIPE. Je n'ai aucune idée pourquoi je ne reçois pas une erreur similaire lorsque je dirige la sortie du tri vers la solution.
Marius Gedminas
Votre version est également plus facile à taper de la mémoire. :-)
Marius Gedminas
Oui, c'est un tuyau cassé. Je ne comprends pas cela avec les versions GNU et BSD de toutes ces commandes, mais c’est la headcommande qui se ferme une fois qu’elle a lu une ligne et "rompt" donc le tuyau, je pense. Vous n'obtenez pas l'erreur parce sortqu'il ne semble pas s'en plaindre, mais lsdans l'autre cas.
Slhck
4
Cela casse s'il y a tellement de noms de fichiers qui xargsdoivent être appelés lsplus d'une fois. Dans ce cas, les sorties triées de ces invocations multiples sont concaténées lorsqu'elles doivent être fusionnées.
Nicole Hamilton
2
Je pense que c'est pire que de publier un script qui suppose que les noms de fichiers ne contiennent jamais d'espaces. Très souvent, cela fonctionnera parce que les noms de fichiers n'ont pas d'espace. Et quand ils échouent, vous obtenez une erreur. Mais il est peu probable que cela fonctionne dans des cas réels et un échec ne sera pas découvert. Sur un arbre de répertoire assez grand que vous ne pouvez pas simplement lset globe oculaire le plus ancien fichier, votre solution probablement va dépasser la limite de longueur de ligne de commande, ce qui lsdoit être invoqué à plusieurs reprises. Vous aurez la mauvaise réponse mais vous ne saurez jamais.
Nicole Hamilton
11

Les commandes suivantes sont garanties pour fonctionner avec tout type de nom de fichier étrange:

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/[^ ]* //')"

L'utilisation d'un byte ( \0) null au lieu d'un caractère de saut de ligne ( \n) garantit que la sortie de find sera toujours compréhensible au cas où l'un des noms de fichier contiendrait un caractère de saut de ligne.

Le -zcommutateur fait en sorte que sort et grep interprètent uniquement les octets nuls en tant que caractères de fin de ligne. Comme il n'y a pas de commutateur de ce type pour la tête, nous utilisons à la grep -m 1place (une seule occurrence).

Les commandes sont classées par heure d'exécution (mesurée sur ma machine).

  • La première commande sera la plus lente car elle doit d'abord convertir chaque fichier mtime en un format lisible par l'homme, puis trier ces chaînes. Piping to Cat évite de colorer la sortie.

  • La deuxième commande est légèrement plus rapide. Tandis qu'il effectue toujours la conversion de date, trie numériquement ( sort -n) les secondes écoulées depuis l'époque Unix est un peu plus rapide. sed supprime les secondes depuis l'époque Unix.

  • La dernière commande ne fait aucune conversion et devrait être nettement plus rapide que les deux premières. La commande find n’affiche pas l’heure mtime du fichier le plus ancien. Par conséquent, stat est nécessaire.

Pages de manuel connexes: Trouver - Grep - Sed - Sort - Stat

Dennis
la source
5

Bien que la réponse acceptée et d’autres ici fonctionnent, si vous avez un très grand arbre, ils trieront l’ensemble des fichiers.

Ce serait mieux si nous pouvions simplement les énumérer et garder une trace des plus anciens, sans qu'il soit nécessaire de les trier.

C'est pourquoi j'ai proposé cette solution alternative:

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}'

J'espère que cela pourra vous aider, même si la question est un peu ancienne.


Edition 1: ces modifications permettent d'analyser les fichiers et les répertoires avec des espaces. Il est assez rapide pour le publier à la racine /et trouver le fichier le plus ancien.

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}'

Commande expliquée:

  • ls -lRU --time-style = long-iso "$ PWD" / * liste tous les fichiers (*), format long (l), récursivement (R), sans que le tri (U) soit rapide, et le dirige vers awk
  • Awk puis BEGIN en remettant à zéro le compteur (facultatif pour cette question) et en définissant la date la plus ancienne à aujourd'hui, formate YearMonthDay.
  • La boucle principale en premier
    • Récupère le 6ème champ, la date, le format Année-Mois-Jour et remplacez-le par YearMonthDay (si votre ls ne s'affiche pas de cette façon, vous devrez peut-être le régler avec précision).
    • En utilisant récursif, il y aura des lignes d’en-tête pour tous les répertoires, sous la forme de / directory / here :. Saisissez cette ligne dans la variable pat. (en remplaçant le dernier ":" par un "/"). Et définit $ 6 sur rien pour éviter d'utiliser la ligne d'en-tête comme ligne de fichier valide.
    • si le champ $ 6 a un numéro valide, c'est une date. Comparez-le avec l'ancienne date oldd.
    • Est-ce plus vieux? Enregistrez ensuite les nouvelles valeurs pour old date oldd et old filename oldf. BTW, Oldf est non seulement 8ème terrain, mais du 8ème à la fin. C'est pourquoi une boucle à concaténer du 8 au NF (fin).
    • Compter les avances par un
    • FIN en imprimant le résultat

Le lancer:

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

Date la plus ancienne: 19691231

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

Total comparé: 111438

real 0m1.135s

utilisateur 0m0.872s

sys 0m0.760s


EDIT 2: Même concept, meilleure solution findpour regarder le temps d’accès (utiliser %Tavec le premier printfpour le temps de modification ou %Cpour changer le statut ).

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}'

EDIT 3: La commande ci-dessous utilise l’ heure de modification et affiche également la progression incrémentielle lorsqu’elle trouve des fichiers de plus en plus anciens, ce qui est utile lorsque vous avez des horodatages incorrects (comme le 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
la source
Il a toujours besoin de tweking pour accepter les fichiers avec des espaces. Je le ferai bientôt.
Dr Beco
Je pense que l'analyse de ls pour les fichiers avec des espaces n'est pas une bonne idée. Peut-être en utilisant find.
Dr Beco
Il suffit de l’exécuter dans tout l’arbre "/" Temps passé: Total comparé: 585744 utilisateurs réels 2m14.017s 0m8.181s sys 0m8.473s
Dr Beco
L'utilisation lsest mauvaise pour les scripts car sa sortie n'est pas destinée aux machines, le formatage de la sortie varie selon les implémentations. Comme vous l’avez déjà dit, findc’est bon pour les scripts, mais il peut également être utile d’ajouter ces informations avant de parler de lssolutions.
Sampo Sarrala
4

Veuillez utiliser ls - la page de manuel vous explique comment commander le répertoire.

ls -clt | head -n 2

Le -n 2 est tel que vous n'obtenez pas le "total" dans la sortie. Si vous voulez seulement le nom du fichier.

ls -t | head -n 1

Et si vous avez besoin de la liste dans l’ordre normal (obtenir le fichier le plus récent)

ls -tr | head -n 1

Bien plus facile que d’utiliser find, beaucoup plus rapide et plus robuste, n’ayez pas à vous soucier des formats de nommage des fichiers. Cela devrait également fonctionner sur presque tous les systèmes.

utilisateur1363990
la source
6
Cela ne fonctionne que si les fichiers sont dans un seul répertoire, alors que ma question portait sur une arborescence de répertoires.
Marius Gedminas
2
find ! -type d -printf "%T@ %p\n" | sort -n | head -n1
Okki
la source
Cela ne fonctionnera pas correctement s'il existe des fichiers antérieurs au 9 septembre 2001 (1000000 000 secondes depuis l'époque Unix). Pour activer le tri numérique, utilisez sort -n.
Dennis
Cela m'aide à retrouver le fichier, mais il est difficile de voir son âge sans exécuter une deuxième commande :)
Marius Gedminas
0

Il semble que par "plus ancien", la plupart des gens ont supposé que vous entendiez par "temps de modification le plus ancien". C'est probablement corrigé, selon l'interprétation la plus stricte de "plus ancien", mais au cas où vous voudriez celui avec le plus long temps d' accès , je modifierais la meilleure réponse ainsi:

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

Remarquez le %A+.

PenguinLust
la source
-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' imprime les dates et les noms de fichiers sur deux colonnes.
  • sort | head -n1 conserve la ligne correspondant au fichier le plus ancien.
  • echo $2 affiche la deuxième colonne, c'est-à-dire le nom du fichier.
Dima
la source
1
Bienvenue sur Super User! Bien que cela puisse répondre à la question, ce serait une meilleure réponse si vous pouviez expliquer pourquoi .
DavidPostill
1
Notez que plusieurs personnes ont également demandé des explications sur votre précédente réponse supprimée (identique).
DavidPostill
Qu'est-ce qui est difficile à répondre? find ./search/dirname -type f -printf '% T +% h /% f \ n' | trier | head -n 1 Affiche deux colonnes comme heure et chemin du fichier. Il est nécessaire de supprimer la première colonne. Utilisation de set et echo $ 2
Dima
1
Vous devez fournir des explications au lieu de simplement coller une ligne de commande, comme demandé par plusieurs autres utilisateurs.
Ob1lan
1
Comment est-ce différent alors la réponse acceptée?
Ramhound