Comment rechercher et lister récursivement les derniers fichiers modifiés dans un répertoire avec des sous-répertoires et des heures?

417
  • Système d'exploitation: Linux

  • Type de système de fichiers: ext3

  • Solution préférée: bash (script / oneliner), ruby, python

J'ai plusieurs répertoires contenant plusieurs sous-répertoires et fichiers. J'ai besoin de faire une liste de tous ces répertoires qui est construite de manière à ce que chaque répertoire de premier niveau soit répertorié à côté de la date et de l'heure du dernier fichier créé / modifié.

Pour clarifier, si je touche un fichier ou modifie son contenu quelques niveaux de sous-répertoire vers le bas, cet horodatage doit être affiché à côté du nom du répertoire de premier niveau. Disons que j'ai un répertoire structuré comme ceci:

./alfa/beta/gamma/example.txt

et je modifie le contenu du fichier example.txt, j'ai besoin de ce temps affiché à côté du répertoire de premier niveau alfasous une forme lisible par l'homme, pas l'époque. J'ai essayé certaines choses en utilisant find, et autres xargs, sortmais je ne peux pas contourner le problème selon lequel l'horodatage du système de fichiers 'alfa' ne change pas lorsque je crée / modifie des fichiers à quelques niveaux plus bas.

Fredrik
la source
Si vous pouvez prendre la peine de le construire, github.com/shadkam/recentmost peut être utilisé.
user3392225
4
Incroyable. 16 réponses, et la plupart / tous n'essaient même pas de faire ce que le PO a spécifié ...
hmijail pleure les démissionnaires
Au lieu de solutions telles qu'un commutateur -R, je vois juste du volume ici.
neverMind9
Devrait être une fonctionnalité native.
neverMind9

Réponses:

486

Essaye celui-là:

#!/bin/bash
find $1 -type f -exec stat --format '%Y :%y %n' "{}" \; | sort -nr | cut -d: -f2- | head

Exécutez-le avec le chemin d'accès au répertoire où il doit commencer à analyser récursivement (il prend en charge les noms de fichiers avec des espaces).

S'il y a beaucoup de fichiers, cela peut prendre un certain temps avant de retourner quoi que ce soit. Les performances peuvent être améliorées si nous utilisons à la xargsplace:

#!/bin/bash
find $1 -type f -print0 | xargs -0 stat --format '%Y :%y %n' | sort -nr | cut -d: -f2- | head

ce qui est un peu plus rapide.

Heppo
la source
132
Votre "méthode rapide" devrait également pouvoir utiliser print0 pour prendre en charge les espaces et même les sauts de ligne dans les noms de fichiers. Voici ce que j'utilise: find $1 -type f -print0 | xargs -0 stat --format '%Y :%y %n' | sort -nr | cut -d: -f2- | head Cela parvient toujours à être rapide pour moi.
Dan
20
Sous Mac OS X, ce n'est pas la statistique de GNU, la commande échoue. Vous devez brew install coreutilset utiliser gstatau lieu destat
CharlesB
36
Vous n'avez pas besoin de courir statcar find PATH -type f -printf "%T@ %p\n"| sort -nrle travail est fait. C'est aussi un peu plus rapide de cette façon.
nr
4
pouvons-nous en quelque sorte transformer le commentaire de @ user37078 en une réponse réelle ou modifier la réponse originale? il semble que ce soit "la bonne façon" [tm] de le faire.
mnagel
6
Sur Mac OS X, sans installer gstat ou quoi que ce soit d'autre, vous pouvez faire:find PATH -type f -exec stat -f "%m %N" "{}" \; | sort -nr | head
cobbzilla
198

Pour rechercher tous les fichiers dont l'état du fichier a été modifié pour la dernière fois il y a N minutes:

find -cmin -N

par exemple:

find -cmin -5

iman
la source
4
+1 Merci, très utile. Fonctionne sur Windows à l'aide de la recherche GnuWin32.
Sabuncu
très laconique. très agréable!
Randy L
c'est plus rapide que d'autres solutions plus compliquées
david.perez
20
Vraiment bien, vous pouvez également utiliser 'find -ctime -50' par exemple pour les 50 derniers jours de changement.
Gorkem
1
Pour exclure l'encombrement, utilisezsudo find -cmin -1 2>&1 |grep -v /proc/
Cees Timmerman
39

GNU Find (voir man find) a un -printfparamètre pour déplacer les fichiers EPOC mtime et le nom de chemin relatif.

redhat> find . -type f -printf '%T@ %P\n' | sort -n | awk '{print $2}'
user2570243
la source
3
Merci! C'est la seule réponse suffisamment rapide pour rechercher dans ma très large structure de répertoires dans un délai raisonnable. Je passe la sortie tailpour empêcher l'impression de milliers de lignes dans la sortie.
sffc
8
Autre commentaire: la awk '{print $2}'partie semble poser problème quand il y a des noms de fichiers avec des espaces. Voici une solution utilisant à la sedplace, et elle find . -type f -printf '%T@ %Tc %P\n' | sort -n | tail | sed -r 's/^.{22}//'
affiche
3
Je pense que ça devrait être en quelque sorte -rn
Bojan Dević
2
La variante -printf est beaucoup plus rapide que d'appeler un processus «stat» à chaque fois - elle a réduit les heures de travail de sauvegarde. Merci de m'avoir informé de cela. J'ai évité la chose awk / sed car je ne suis préoccupé que par la dernière mise à jour dans l'arborescence - donc X = $ (find / path -type f -printf '% T% p \ n' | grep -v something-I- don-tcare-about | sort -nr | head -n 1) et un écho $ {X # * ""} ont bien fonctionné pour moi (donnez-moi des trucs jusqu'au premier espace)
David Goodwin
2
Tout ne fonctionnera pas si le nom de fichier sur plusieurs lignes. Utilisez touch "lala<Enter>b"pour créer un tel fichier. Je pense que la conception des utilitaires unix a un gros défaut sur le nom de fichier.
Fruit
35

J'ai raccourci la réponse impressionnante de halo à ce one-liner

stat --printf="%y %n\n" $(ls -tr $(find * -type f))

Mise à jour : s'il y a des espaces dans les noms de fichiers, vous pouvez utiliser cette modification

OFS="$IFS";IFS=$'\n';stat --printf="%y %n\n" $(ls -tr $(find . -type f));IFS="$OFS";
slashdottir
la source
Que diriez-vous de ceci: IFS = $ '\ n'; stat --printf = "% y% n \ n" $ (ls -tr $ (find. -type f))
slashdottir
3
Cela ne fonctionnera pas si vous avez un très grand nombre de fichiers. les réponses qui utilisent des xargs résolvent cette limite.
carl verbiest le
@carlverbiest en effet un grand nombre de fichiers vont casser la solution de slashdottir. Même les solutions basées sur xargs seront alors lentes. La solution de user2570243 est la meilleure pour les gros systèmes de fichiers.
Stéphane Gourichon
IFS=$'\n'n'est en aucun cas sûr lors de la gestion des noms de fichiers: les retours à la ligne sont des caractères valides dans les noms de fichiers sous UNIX. Seul le caractère NUL est garanti de ne pas être présent dans un chemin.
Charles Duffy
17

Essaye ça

#!/bin/bash
stat --format %y $(ls -t $(find alfa/ -type f) | head -n 1)

Il permet findde rassembler tous les fichiers du répertoire, lsde les lister triés par date de modification, headde sélectionner le 1er fichier et enfin statd'afficher l'heure dans un joli format.

À l'heure actuelle, il n'est pas sûr pour les fichiers contenant des espaces ou d'autres caractères spéciaux dans leurs noms. Écrivez un éloge s'il ne répond pas encore à vos besoins.

Daniel Böhmer
la source
1
halo: J'aime votre réponse, elle fonctionne bien et imprime le bon fichier. Je ne m'aide pas cependant car il y a trop de sous-niveaux dans mon cas. Je reçois donc "Liste d'arguments trop longue" pour ls ... et xargs n'aiderait pas non plus dans ce cas. Je vais essayer autre chose.
fredrik
Dans ce cas, c'est un peu plus complexe et aura besoin d'un vrai programme. Je vais pirater du Perl.
Daniel Böhmer
1
J'ai résolu cela en utilisant PHP à la place. Une fonction récursive qui descend dans l'arborescence du système de fichiers et stocke l'heure du dernier fichier modifié.
fredrik
11

Cette commande fonctionne sur Mac OS X:

find "$1" -type f -print0 | xargs -0 stat --format '%Y :%y %n' | sort -nr | cut -d: -f2- | head

Sous Linux, comme l'a demandé l'affiche originale, utilisez statplutôt gstat.

Cette réponse est, bien sûr, la solution exceptionnelle de user37078 , promue du commentaire à la réponse complète. J'ai mélangé la perspicacité de CharlesB à utiliser gstatsur Mac OS X. J'ai d'ailleurs obtenu des coreutils de MacPorts plutôt que de homebrew .

Et voici comment j'ai empaqueté ceci dans une commande simple ~/bin/ls-recent.shpour la réutilisation:

#!/bin/bash
# ls-recent: list files in a dir tree, most recently modified first
#
# Usage: ls-recent path [-10 | more]
# 
# Where "path" is a path to target directory, "-10" is any arg to pass
# to "head" to limit the number of entries, and "more" is a special arg
# in place of "-10" which calls the pager "more" instead of "head".
if [ "more" = "$2" ]; then
   H=more; N=''
else
   H=head; N=$2
fi

find "$1" -type f -print0 |xargs -0 gstat --format '%Y :%y %n' \
    |sort -nr |cut -d: -f2- |$H $N
Jim DeLaHunt
la source
2
Sous OS X yosemite; J'obtiens une erreur: trouver: ftsopen: Aucun fichier ou répertoire de ce type
Reece
Intéressant. Quelle commande avez-vous tapé (avec des paramètres)? Et quels étaient les noms des fichiers dans ce répertoire? Et si vous avez créé votre propre version de ~/bin/ls-recent.sh, avez-vous soigneusement vérifié les différences dans le script?
Jim DeLaHunt
10
pour ceux qui ne veulent rien installer sur Mac OS X:find . -exec stat -f '%m%t%Sm %N' {} + | sort -n | cut -f2-
Jake
5

Les solutions Perl et Python de cet article m'ont aidé à résoudre ce problème sur Mac OS X: /unix/9247/how-to-list-files-sorted-by-modification-date-recursively -no-stat-command-avail .

Citant de la poste:

Perl:

find . -type f -print |
perl -l -ne '
    $_{$_} = -M;  # store file age (mtime - now)
    END {
        $,="\n";
        print sort {$_{$b} <=> $_{$a}} keys %_;  # print by decreasing age
    }'

Python:

find . -type f -print |
python -c 'import os, sys; times = {}
for f in sys.stdin.readlines(): f = f[0:-1]; times[f] = os.stat(f).st_mtime
for f in sorted(times.iterkeys(), key=lambda f:times[f]): print f'
William Niu
la source
5

Ignorer les fichiers cachés - avec un horodatage agréable et rapide

Gère bien les espaces dans les noms de fichiers - pas que vous devez les utiliser!

$ find . -type f -not -path '*/\.*' -printf '%TY.%Tm.%Td %THh%TM %Ta %p\n' |sort -nr |head -n 10

2017.01.28 07h00 Sat ./recent
2017.01.21 10h49 Sat ./hgb
2017.01.16 07h44 Mon ./swx
2017.01.10 18h24 Tue ./update-stations
2017.01.09 10h38 Mon ./stations.json

Plus à findgogo peut être trouvé en suivant le lien.

Serge Stroobandt
la source
3

Je montre ceci pour le dernier temps d'accès, vous pouvez facilement le modifier pour faire le dernier temps de mod.

Il y a deux façons de procéder:


1) Si vous voulez éviter le tri global qui peut coûter cher si vous avez des dizaines de millions de fichiers, alors vous pouvez faire: (positionnez-vous à la racine du répertoire où vous souhaitez que votre recherche démarre)

linux> touch -d @0 /tmp/a;
linux> find . -type f -exec tcsh -f -c test `stat --printf="%X" {}` -gt  `stat --printf="%X" /tmp/a`  ; -exec tcsh -f -c touch -a -r {} /tmp/a ; -print 

La méthode ci-dessus imprime les noms de fichiers avec un temps d'accès progressivement plus récent et le dernier fichier qu'elle imprime est le fichier avec le dernier temps d'accès. Vous pouvez évidemment obtenir le dernier temps d'accès en utilisant un "tail -1".


2) Vous pouvez avoir à rechercher récursivement le nom, l'heure d'accès de tous les fichiers de votre sous-répertoire, puis trier en fonction de l'heure d'accès et de la queue la plus grande entrée:

linux> \find . -type f -exec stat --printf="%X  %n\n" {} \; | \sort -n | tail -1

Et voila...

Sean
la source
3

J'ai cet alias dans mon .profile que j'utilise assez souvent

$ alias | grep xlogs
xlogs='sudo find . \( -name "*.log" -o -name "*.trc" \) -mtime -1 | sudo xargs ls -ltr --color | less -R'

Donc, il fait ce que vous recherchez (à l'exception qu'il ne traverse pas plusieurs niveaux de changement de date / heure) - recherche les derniers fichiers (fichiers * .log et * .trc dans ce cas); De plus, il ne trouve que les fichiers modifiés le dernier jour, puis les trie par heure et les tuyaux sortent par moins:

sudo find . \( -name "*.log" -o -name "*.trc" \) -mtime -1 | sudo xargs ls -ltr --color | less -R

ps. Remarquez que je n'ai pas de racine sur certains serveurs, mais que j'ai toujours sudo, donc vous n'aurez peut-être pas besoin de cette partie.

Tagar
la source
Comment est-ce "exactement ce que vous recherchez"? L'OP a écrit une bonne explication de ce qu'il voulait, et cela l'ignore totalement.
hmijail pleure les démissionnaires
merci d'avoir signalé cela. vous avez raison - cette méthode ne va pas sur plusieurs niveaux pour obtenir la date / heure de changement, elle n'affiche que la date / heure des fichiers des répertoires. édité ma réponse.
Tagar
1

Vous pouvez donner la commande printf de trouver un essai

Dernière heure d'accès du fichier% Ak au format spécifié par k, qui est soit @' or a directive for the C la fonction strftime. Les valeurs possibles pour k sont répertoriées ci-dessous; certains d'entre eux peuvent ne pas être disponibles sur tous les systèmes, en raison des différences de «strftime» entre les systèmes.

graugans
la source
1

Fonction bash rapide:

# findLatestModifiedFiles(directory, [max=10, [format="%Td %Tb %TY, %TT"]])
function findLatestModifiedFiles() {
    local d="${1:-.}"
    local m="${2:-10}"
    local f="${3:-%Td %Tb %TY, %TT}"

    find "$d" -type f -printf "%T@ :$f %p\n" | sort -nr | cut -d: -f2- | head -n"$m"
}

Recherchez le dernier fichier modifié dans un répertoire:

findLatestModifiedFiles "/home/jason/" 1

Vous pouvez également spécifier votre propre format de date / heure comme troisième argument.

Jason Larke
la source
1

Ce qui suit vous renvoie une chaîne d'horodatage et le nom du fichier avec l'horodatage le plus récent:

find $Directory -type f -printf "%TY-%Tm-%Td-%TH-%TM-%TS %p\n" | sed -r 's/([[:digit:]]{2})\.([[:digit:]]{2,})/\1-\2/' |     sort --field-separator='-' -nrk1 -nrk2 -nrk3 -nrk4 -nrk5 -nrk6 -nrk7 | head -n 1

Résultat d'une sortie du formulaire: <yy-mm-dd-hh-mm-ss.nanosec> <filename>

mark_infinite
la source
1

Voici une version qui fonctionne avec des noms de fichiers qui peuvent également contenir des espaces, des sauts de ligne, des caractères globaux:

find . -type f -printf "%T@ %p\0" | sort -zk1nr
  • find ... -printfimprime la modification de fichier (valeur EPOCH) suivie d'un espace et de \0noms de fichiers terminés.
  • sort -zk1nr lit les données terminées NUL et les trie numériquement

Comme la question est balisée avec Linux, je suppose que des gnuutilitaires sont disponibles.

Vous pouvez pipe ci-dessus avec:

xargs -0 printf "%s\n"

pour imprimer l'heure de modification et les noms de fichiers triés par heure de modification (la plus récente en premier) terminée par des retours à la ligne.

anubhava
la source
1

Voici ce que j'utilise (très efficace):

function find_last () { find "${1:-.}" -type f -printf '%TY-%Tm-%Td %TH:%TM %P\n' 2>/dev/null | sort | tail -n "${2:-10}" }

AVANTAGES:

  • il ne génère que 3 processus

USAGE:

find_last [dir [number]]

où:

  • dir - un répertoire à rechercher [répertoire actuel]
  • number - nombre de fichiers les plus récents à afficher [10]

La sortie pour find_last /etc 4ressemble à ceci:

2019-07-09 12:12 cups/printers.conf
2019-07-09 14:20 salt/minion.d/_schedule.conf
2019-07-09 14:31 network/interfaces
2019-07-09 14:41 environment
Seweryn Niemiec
la source
0

Pour une lssortie simple , utilisez ceci. Il n'y a pas de liste d'arguments, donc ça ne peut pas être trop long:

find . | while read FILE;do ls -d -l "$FILE";done

Et gentiment avec cutjuste pour les dates, les heures et le nom:

find . | while read FILE;do ls -d -l "$FILE";done | cut --complement -d ' ' -f 1-5

EDIT : Je viens de remarquer que la première réponse actuelle est triée par date de modification. C'est tout aussi simple avec le deuxième exemple ici, car la date de modification est la première sur chaque ligne - claquez un tri à la fin:

find . | while read FILE;do ls -d -l "$FILE";done | cut --complement -d ' ' -f 1-5 | sort
Izkata
la source
0

Cela pourrait être fait avec une fonction récursive dans bash aussi

Soit F une fonction qui affiche l'heure du fichier qui doit être triable lexicographiquement aaaa-mm-jj etc., (dépendant du système d'exploitation?)

F(){ stat --format %y "$1";}                # Linux
F(){ ls -E "$1"|awk '{print$6" "$7}';}      # SunOS: maybe this could be done easier

R la fonction récursive qui traverse les répertoires

R(){ local f;for f in "$1"/*;do [ -d "$f" ]&&R $f||F "$f";done;}

et enfin

for f in *;do [ -d "$f" ]&&echo `R "$f"|sort|tail -1`" $f";done
Nahuel Fouilleul
la source