Come trovare ricorsivamente ed elencare gli ultimi file modificati in una directory con sottodirectory e orari?


417
  • Sistema operativo: Linux

  • Tipo di filesystem: ext3

  • Soluzione preferita: bash (script / oneliner), ruby, python

Ho diverse directory con diverse sottodirectory e file. Ho bisogno di fare un elenco di tutte queste directory che è costruito in modo tale che ogni directory di primo livello sia elencata accanto alla data e ora dell'ultimo file creato / modificato al suo interno.

Per chiarire, se tocco un file o ne modifico il contenuto di alcuni livelli della sottodirectory in basso, quel timestamp dovrebbe essere visualizzato accanto al nome della directory di primo livello. Supponiamo di avere una directory strutturata in questo modo:

./alfa/beta/gamma/example.txt

e modifico il contenuto del file example.txt, ho bisogno che il tempo alfavenga visualizzato accanto alla directory di primo livello in forma leggibile dall'uomo, non in epoca. Ho provato alcune cose usando find, xargs, sorte simili, ma non riesco a ottenere intorno al problema che il timestamp filesystem di 'alfa' non cambia quando creo / modificare i file di alcuni livelli verso il basso.


Se puoi prenderti il ​​disturbo di costruirlo, puoi usare github.com/shadkam/recentmost .
user3392225

4
Incredibile. 16 risposte, e la maggior parte / tutti non provano nemmeno a fare ciò che l'OP ha specificato ...
hmijail piange le dimissioni il

Invece di soluzioni come un interruttore -R, vedo solo bulk qui.
neverMind9

Dovrebbe essere una funzionalità nativa.
neverMind9

Risposte:


486

Prova questo:

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

Eseguilo con il percorso della directory in cui dovrebbe iniziare la scansione in modo ricorsivo (supporta nomi di file con spazi).

Se ci sono molti file, potrebbe volerci un po 'di tempo prima che restituisca qualcosa. Le prestazioni possono essere migliorate se xargsinvece utilizziamo :

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

che è un po 'più veloce.


132
Il tuo "metodo veloce" dovrebbe anche essere in grado di utilizzare print0 per supportare spazi e persino avanzamenti di riga nei nomi di file. Ecco cosa uso: find $1 -type f -print0 | xargs -0 stat --format '%Y :%y %n' | sort -nr | cut -d: -f2- | head questo riesce ancora ad essere veloce per me.
Dan,

20
Su Mac OS X non è la stat di GNU, quindi il comando fallisce. Devi brew install coreutilse usare gstatinvece distat
CharlesB

36
Non è necessario eseguire statpoiché find PATH -type f -printf "%T@ %p\n"| sort -nrfa il lavoro. È anche un po 'più veloce in questo modo.
n.

4
possiamo in qualche modo trasformare il commento di @ user37078 in una risposta effettiva o modificare la risposta originale? sembra essere "il modo giusto" [tm] per farlo.
mnagel,

6
Su Mac OS X, senza installare gstat o altro, puoi fare:find PATH -type f -exec stat -f "%m %N" "{}" \; | sort -nr | head
cobbzilla

198

Per trovare tutti i file per cui lo stato del file è stato modificato l'ultima volta N minuti fa:

find -cmin -N

per esempio:

find -cmin -5


4
+1 Grazie, molto utile. Funziona su Windows usando GnuWin32 find.
Sabuncu,

molto conciso. molto bella!
Randy L

è più veloce di altre soluzioni più complicate
david.perez,

20
Davvero buono, puoi anche usare 'find -ctime -50' per esempio per gli ultimi 50 giorni di modifica.
Gorkem,

1
Per escludere il disordine, usasudo find -cmin -1 2>&1 |grep -v /proc/
Cees Timmerman il

39

GNU Find (vedi man find) ha un -printfparametro per disporre i file EPOC mtime e il relativo nome del percorso.

redhat> find . -type f -printf '%T@ %P\n' | sort -n | awk '{print $2}'

3
Grazie! Questa è l'unica risposta che è abbastanza veloce da cercare nella mia struttura di directory molto ampia in un tempo ragionevole. Passo attraverso l'output tailper impedire la stampa di migliaia di righe nell'output.
Sffc,

8
Un altro commento: la awk '{print $2}'parte sembra causare problemi quando ci sono nomi di file con spazi. Ecco una soluzione che utilizza sedinvece e stampa anche l'ora oltre al percorso:find . -type f -printf '%T@ %Tc %P\n' | sort -n | tail | sed -r 's/^.{22}//'
sffc

3
Penso che dovrebbe essere una specie -rn
Bojan Dević,

2
La variante -printf è molto più rapida della chiamata a un processo 'stat' ogni volta - taglia le ore ai miei processi di backup. Grazie per avermi informato di questo. Ho evitato la cosa awk / sed perché sono preoccupato solo per l'ultimo aggiornamento all'interno dell'albero - quindi X = $ (find / path -type f -printf '% T% p \ n' | grep -v qualcosa-I- don-tcare-about | sort -nr | head -n 1) e un'eco $ {X # * ""} hanno funzionato bene per me (dammi roba fino al primo spazio)
David Goodwin

2
Tutto non funzionerà se il nome file su più righe. Usotouch "lala<Enter>b" per creare tale file. Penso che il design delle utility unix abbia un grosso difetto nel nome del file.
Frutta

35

Ho abbreviato la fantastica risposta dell'alone a questo one-liner

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

Aggiornato : se ci sono spazi nei nomi dei file, puoi usare questa modifica

OFS="$IFS";IFS=$'\n';stat --printf="%y %n\n" $(ls -tr $(find . -type f));IFS="$OFS";

Che ne dici di questo: IFS = $ '\ n'; stat --printf = "% y% n \ n" $ (ls -tr $ (find. -type f))
slashdottir

3
Questo non funzionerà se si dispone di un numero molto elevato di file. le risposte che usano xargs risolvono quel limite.
Carl Verbiest,

@carlverbiest infatti un gran numero di file romperà la soluzione di slashdottir. Anche le soluzioni basate su xargs saranno lente allora. La soluzione di user2570243 è la migliore per file system di grandi dimensioni.
Stéphane Gourichon,

IFS=$'\n'non è sicuro in nessun caso quando si gestiscono nomi di file: le nuove righe sono caratteri validi nei nomi di file su UNIX. Solo il carattere NUL è garantito per non essere presente in un percorso.
Charles Duffy,

17

Prova questo

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

Usa findper raccogliere tutti i file dalla directory, lsper elencarli ordinati per data di modifica, headper selezionare il 1 ° file e infinestat per mostrare l'ora in un bel formato.

Al momento non è sicuro per i file con spazi bianchi o altri caratteri speciali nei loro nomi. Scrivi un encomio se non soddisfa ancora le tue esigenze.


1
alone: ​​mi piace la tua risposta, funziona bene e stampa il file corretto. Tuttavia non mi aiuta perché ci sono troppi livelli secondari nel mio caso. Quindi ottengo "Elenco argomenti troppo lungo" per ls ... e anche xargs non sarebbe d'aiuto in questo caso. Proverò qualcos'altro.
Fredrik,

In quel caso è un po 'più complesso e avrà bisogno di un vero programma. Intaccherò un po 'di Perl.
Daniel Böhmer,

1
Ho risolto questo usando PHP invece. Una funzione ricorsiva che scende attraverso l'albero del filesystem e memorizza l'ora dell'ultimo file modificato.
Fredrik,

11

Questo comando funziona su Mac OS X:

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

Su Linux, come richiesto dal poster originale, utilizzare statinvece di gstat.

Questa risposta è, naturalmente, la straordinaria soluzione di user37078 , promossa da commento a risposta completa. Ho mescolato in charlesb intuizione s' per l'uso gstatsu Mac OS X. Ho ottenuto coreutils da MacPorts piuttosto che homebrew , tra l'altro.

Ed ecco come l'ho impacchettato in un semplice comando ~/bin/ls-recent.shper il riutilizzo:

#!/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

2
Su OS X yosemite; Ottengo l'errore: find: ftsopen: nessun file o directory simile
Reece

Interessante. Quale comando hai digitato (con parametri)? E quali erano i nomi dei file in quella directory? E se hai creato la tua versione di ~/bin/ls-recent.sh, hai controllato attentamente le differenze nello script?
Jim DeLaHunt

10
per coloro che non vogliono installare nulla su Mac OS X:find . -exec stat -f '%m%t%Sm %N' {} + | sort -n | cut -f2-
Jake,

5

Entrambe le soluzioni perl e Python in questo post mi hanno aiutato a risolvere questo problema su Mac OS X: /unix/9247/how-to-list-files-sorted-by-modification-date-recursively -no-stat-command-util .

Citando dal post:

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

Pitone:

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'

5

Ignorare i file nascosti - con timestamp piacevole e veloce

Gestisce bene gli spazi nei nomi dei file - non che dovresti usarli!

$ 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

Più findbizzeffe si possono trovare seguendo il link.


3

Lo sto mostrando per i tempi di accesso più recenti, puoi facilmente modificarlo per eseguire le ultime modifiche.

Esistono due modi per farlo:


1) Se vuoi evitare l'ordinamento globale che può essere costoso se hai decine di milioni di file, allora puoi fare: (posizionati nella radice della directory dove vuoi che inizi la ricerca)

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 

Il metodo sopra stampa i nomi dei file con tempi di accesso progressivamente più recenti e l'ultimo file stampato è il file con i tempi di accesso più recenti. Ovviamente puoi ottenere gli ultimi tempi d'accesso usando una "coda -1".


2) Puoi trovare in modo ricorsivo stampare il nome, il tempo di accesso di tutti i file nella tua sottodirectory e quindi ordinare in base al tempo di accesso e alla coda la voce più grande:

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

E il gioco è fatto...


3

Ho questo alias nel mio .profile che uso abbastanza spesso

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

Quindi fa quello che stai cercando (con l'eccezione che non attraversa la data / ora cambia più livelli) - cerca i file più recenti (file * .log e * .trc in questo caso); inoltre trova solo i file modificati nell'ultimo giorno, quindi ordina in base al tempo e le pipe vengono emesse in meno:

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

ps. Nota che non ho root su alcuni server, ma ho sempre sudo, quindi potresti non aver bisogno di quella parte.


Come è "esattamente quello che stai cercando"? L'OP ha scritto una buona spiegazione di ciò che voleva, e questo lo ignora totalmente.
Hmijail piange le dimissioni il

grazie per averlo indicato. hai ragione - questo metodo non va su più livelli per ottenere la modifica della data / ora, mostra solo la data / ora dei file delle directory al suo interno. modificato la mia risposta.
Tagar,

1

Puoi dare il comando printf di find try

L'ultimo tempo di accesso di File Ak nel formato specificato da k, che è una @' or a directive for the C delle funzioni strftime. I possibili valori per k sono elencati di seguito; alcuni potrebbero non essere disponibili su tutti i sistemi, a causa delle differenze di "durata" tra i sistemi.


1

Funzione bash veloce:

# 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"
}

Trova l'ultimo file modificato in una directory:

findLatestModifiedFiles "/home/jason/" 1

Puoi anche specificare il tuo formato data / ora come terzo argomento.


1

Di seguito viene restituita una stringa del timestamp e il nome del file con il timestamp più recente:

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

Risultato di un output del modulo: <yy-mm-dd-hh-mm-ss.nanosec> <filename>


1

Ecco una versione che funziona con nomi di file che possono contenere spazi, newline e caratteri glob:

find . -type f -printf "%T@ %p\0" | sort -zk1nr
  • find ... -printfstampa la modifica del file (valore EPOCH) seguita da uno spazio e dai \0nomi file terminati.
  • sort -zk1nr legge i dati terminati NUL e li ordina in ordine numerico

Poiché la domanda è taggata con Linux, quindi presumo che gnusiano disponibili utilità.

Puoi eseguire il pipe sopra con:

xargs -0 printf "%s\n"

per stampare il tempo di modifica e i nomi dei file ordinati in base al tempo di modifica (primo più recente) terminato da newline.


1

Questo è quello che sto usando (molto efficiente):

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

PROFESSIONISTI:

  • genera solo 3 processi

USO:

find_last [dir [number]]

dove:

  • dir - una directory da cercare [dir corrente]
  • number - numero di file più recenti da visualizzare [10]

L'output find_last /etc 4è simile al seguente:

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

0

Per un lsoutput semplice , utilizzare questo. Non esiste un elenco di argomenti, quindi non può richiedere troppo tempo:

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

E perfezionato con cutsolo le date, gli orari e il nome:

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

EDIT : ho appena notato che l'attuale risposta principale ordina per data di modifica. È altrettanto facile con il secondo esempio qui, poiché la data di modifica è la prima su ogni riga: schiaccia un ordinamento alla fine:

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

0

Questo potrebbe essere fatto con una funzione ricorsiva anche in bash

Sia F una funzione che visualizza il tempo del file che deve essere lessicograficamente ordinabile aaaa-mm-gg ecc., (Dipendente dal sistema operativo?)

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

R la funzione ricorsiva che passa attraverso le directory

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

E infine

for f in *;do [ -d "$f" ]&&echo `R "$f"|sort|tail -1`" $f";done
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.