Come posso trovare il file più vecchio in un albero di directory


72

Sto cercando una shell one-liner per trovare il file più vecchio in un albero di directory.

Risposte:


72

Funziona (aggiornato per includere il suggerimento di Daniel Andersson):

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

8
Meno battitura a macchina:find -type f -printf '%T+ %p\n' | sort | head -1
Daniel Andersson,

1
Ottengo spazio vuoto perché la mia prima riga da questo findè vuota a causa del fatto che ho nome file contiene newline.
林果 皞

1
Posso chiedere se utilizza la data di creazione o modifica?
MrMesees,

1
Linux non memorizza la data di creazione del file da nessuna parte [*]. Questo utilizza la data di modifica. [*] questo non è in realtà vero; ext4 memorizza la data di creazione dell'inode, ma non è esposta tramite alcuna chiamata di sistema ed è necessario utilizzare debugfs per vederlo.)
Marius Gedminas

11

Questo è un po 'più portatile e perché non si basa su GNU findestensione -printf, quindi funziona su BSD / OS X così:

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

L'unico aspetto negativo qui è che è in qualche modo limitato alla dimensione di ARG_MAX(che dovrebbe essere irrilevante per la maggior parte dei kernel più recenti). Quindi, se vengono getconf ARG_MAXrestituiti più di caratteri (262.144 sul mio sistema), non si ottiene il risultato corretto. Inoltre, non è conforme a POSIX perché -print0e xargs -0non lo è.

Alcune ulteriori soluzioni a questo problema sono delineate qui: Come posso trovare il file più recente (più recente, più vecchio, più vecchio) in una directory? - Greg's Wiki


Anche questo funziona, ma emette anche un xargs: ls: terminated by signal 13errore come effetto collaterale. Immagino che sia SIGPIPE. Non ho idea del motivo per cui non ottengo un errore simile quando installo l'output di sort per dirigere nella mia soluzione.
Marius Gedminas,

La tua versione è anche più facile da digitare dalla memoria. :-)
Marius Gedminas,

Sì, è un tubo rotto. Non capisco con entrambe le versioni GNU e BSD di tutti quei comandi, ma è il headcomando che si chiude una volta che ha letto una riga e quindi "rompe" la pipe, credo. Non si ottiene l'errore perché sortnon sembra lamentarsene, ma lsnell'altro caso.
slhck,

4
Questo si interrompe se ci sono così tanti nomi di file che xargsdevono invocare lspiù di una volta. In tal caso, gli output ordinati di tali invocazioni multiple finiscono per essere concatenati quando dovrebbero essere uniti.
Nicole Hamilton,

2
Penso che sia peggio che pubblicare una sceneggiatura che presume che i nomi dei file non contengano mai spazi. Molte volte, funzioneranno perché i nomi dei file non hanno spazi. E quando falliscono, ricevi un errore. Ma è improbabile che ciò funzioni in casi reali e il fallimento non sarà scoperto. Su qualsiasi albero di directory abbastanza grande da non poterlo solo lse controllare il file più vecchio, la soluzione probabilmente supererà il limite di lunghezza della riga di comando, causando lsl'invocazione più volte. Avrai la risposta sbagliata ma non lo saprai mai.
Nicole Hamilton,

11

I seguenti comandi comandi sono garantiti per funzionare con qualsiasi tipo di nomi di file strani:

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'uso di un byte null ( \0) anziché un carattere di avanzamento riga ( \n) assicura che l'output di find sia ancora comprensibile nel caso in cui uno dei nomi di file contenga un carattere di avanzamento riga.

L' -zopzione fa in modo che sia l'ordinamento che il grep interpretino solo i byte null come caratteri di fine riga. Dal momento che non esiste un tale interruttore per head, usiamo grep -m 1invece (solo un'occorrenza).

I comandi sono ordinati in base al tempo di esecuzione (misurato sulla mia macchina).

  • Il primo comando sarà il più lento poiché deve prima convertire ogni mtime di ogni file in un formato leggibile dall'uomo e quindi ordinare quelle stringhe. Il piping su cat evita di colorare l'output.

  • Il secondo comando è leggermente più veloce. Mentre esegue ancora la conversione della data, l'ordinamento numerico ( sort -n) dei secondi trascorsi dall'epoca di Unix è un po 'più veloce. sed cancella i secondi dall'epoca di Unix.

  • L'ultimo comando non esegue alcuna conversione e dovrebbe essere significativamente più veloce dei primi due. Il comando find stesso non visualizzerà il mtime del file più vecchio, quindi è necessario stat.

Pagine man relative a: trovare - grep - sed - specie - stat


5

Anche se la risposta accettata e altri qui fanno il lavoro, se hai un albero molto grande, tutti ordineranno l'intero gruppo di file.

Meglio sarebbe se potessimo semplicemente elencarli e tenere traccia dei più vecchi, senza la necessità di ordinare affatto.

Ecco perché mi è venuta in mente questa soluzione alternativa:

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

Spero possa essere di qualche aiuto, anche se la domanda è un po 'vecchia.


Modifica 1: queste modifiche consentono di analizzare file e directory con spazi. È abbastanza veloce da emetterlo nella radice /e trovare il file più vecchio di sempre.

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

Comando spiegato:

  • ls -lRU --time-style = long-iso "$ PWD" / * elenca tutti i file (*), il formato lungo (l), ricorsivamente (R), senza che l'ordinamento (U) sia veloce e lo instrada per awk
  • Awk quindi INIZIA azzerando il contatore (facoltativo per questa domanda) e impostando la data più vecchia che deve essere oggi, formatta YearMonthDay.
  • Il loop principale per primo
    • Cattura il sesto campo, la data, il formato Anno-Mese-Giorno e modificalo in YearMonthDay (se il tuo ls non emette in questo modo, potrebbe essere necessario perfezionarlo).
    • Usando ricorsivo, ci saranno righe di intestazione per tutte le directory, sotto forma di / directory / qui :. Prendi questa linea in una variabile pat. (sostituendo l'ultimo ":" a un "/"). E imposta $ 6 su nulla per evitare di utilizzare la riga di intestazione come una riga di file valida.
    • se il campo $ 6 ha un numero valido, è una data. Confrontalo con la vecchia data oldd.
    • È più vecchio? Quindi salva i nuovi valori per oldd olddata e oldf vecchiofile. A proposito, oldf non è solo l'ottavo campo, ma dall'8 ° alla fine. Ecco perché un loop per concatenare dall'8 ° al NF (fine).
    • Conta i progressi di uno
    • FINE stampando il risultato

Eseguendolo:

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

Data più antica: 19691231

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

Totale rispetto: 111438

0m1.135s reali

utente 0m0.872s

sys 0m0.760s


EDIT 2: Stesso concetto, soluzione migliore usando findper guardare il tempo di accesso (usare invece %Til primo printfper il tempo di modifica o %Cper il cambio di stato ).

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: Il comando muggito utilizza il tempo di modifica e stampa anche i progressi incrementali quando trova file più vecchi e più vecchi, il che è utile quando si hanno dei timestamp errati (come 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}'

Ha ancora bisogno di tweek per accettare i file con spazi. Lo farò presto.
Dr Beco,

Penso che analizzare ls per file con spazi non sia una buona idea. Forse usando find.
Dr Beco,

Basta eseguirlo nell'intero albero "/". Tempo trascorso: totale confrontato: 585744 utenti reali 2m14.017s 0m8.181s sys 0m8.473s
Dr Beco

L'uso lsè negativo per gli script poiché l'output non è destinato alle macchine, la formattazione dell'output varia a seconda delle implementazioni. Come hai già detto, findè buono per gli script, ma potrebbe anche essere utile aggiungere tali informazioni prima di parlare delle lssoluzioni.
Sampo Sarrala,

4

Per favore usa ls - la pagina man ti dice come ordinare la directory.

ls -clt | head -n 2

-N 2 è quindi non si ottiene il "totale" nell'output. Se vuoi solo il nome del file.

ls -t | head -n 1

E se hai bisogno dell'elenco nell'ordine normale (ottenere il file più recente)

ls -tr | head -n 1

Molto più facile dell'utilizzo di find, molto più veloce e più robusto - non devi preoccuparti dei formati di denominazione dei file. Dovrebbe funzionare anche su quasi tutti i sistemi.


6
Funziona solo se i file si trovano in una singola directory, mentre la mia domanda riguardava un albero di directory.
Marius Gedminas,

2
find ! -type d -printf "%T@ %p\n" | sort -n | head -n1

Questo non funzionerà correttamente se ci sono file più vecchi del 9 settembre 2001 (1000000000 secondi dall'epoca Unix). Per abilitare l'ordinamento numerico, utilizzare sort -n.
Dennis,

Questo mi aiuta a trovare il file, ma è difficile vedere quanti anni ha senza eseguire un secondo comando :)
Marius Gedminas,

0

Sembra che per "più vecchio" la maggior parte delle persone abbia ipotizzato che intendessi "tempo di modifica più vecchio". Questo è probabilmente corretto, secondo l'interpretazione più rigorosa di "più vecchio", ma nel caso volessi quello con il tempo di accesso più vecchio , modificherei così la risposta migliore:

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

Notare il %A+.


-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' stampa le date e i nomi dei file in due colonne.
  • sort | head -n1 mantiene la riga corrispondente al file più vecchio.
  • echo $2 visualizza la seconda colonna, ovvero il nome del file.

1
Benvenuto in Super User! Sebbene ciò possa rispondere alla domanda, sarebbe una risposta migliore se tu potessi fornire qualche spiegazione sul perché lo faccia.
DavidPostill

1
Nota, diverse persone hanno anche chiesto una spiegazione della tua precedente (identica) risposta eliminata.
DavidPostill

A cosa è difficile rispondere? find ./search/dirname -type f -printf '% T +% h /% f \ n' | ordina | head -n 1 Mostra due colonne come l'ora e il percorso del file. È necessario rimuovere la prima colonna. Usando set ed echo $ 2
Dima

1
Dovresti fornire spiegazioni anziché semplicemente incollare una riga di comando, come richiesto da molti altri utenti.
Ob1lan,

1
In che modo differisce dalla risposta accettata?
Ramhound,
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.