Nella shell, come posso taill'ultimo file creato in una directory?
Nella shell, come posso taill'ultimo file creato in una directory?
Risposte:
Evitare Non analizzare l'output di ls! L'analisi dell'output di ls è difficile e inaffidabile .
Se devi farlo, ti consiglio di usare find. Inizialmente avevo qui un semplice esempio semplicemente per darvi il senso della soluzione, ma poiché questa risposta sembra piuttosto popolare, ho deciso di rivederlo per fornire una versione sicura da copiare / incollare e utilizzare con tutti gli input. Sei seduto comodamente? Inizieremo con un oneliner che ti fornirà l'ultimo file nella directory corrente:
tail -- "$(find . -maxdepth 1 -type f -printf '%T@.%p\0' | sort -znr -t. -k1,2 | while IFS= read -r -d '' -r record ; do printf '%s' "$record" | cut -d. -f3- ; break ; done)"
Non è proprio un oneliner ora, vero? Eccolo di nuovo come funzione shell e formattato per una lettura più semplice:
latest-file-in-directory () {
find "${@:-.}" -maxdepth 1 -type f -printf '%T@.%p\0' | \
sort -znr -t. -k1,2 | \
while IFS= read -r -d '' -r record ; do
printf '%s' "$record" | cut -d. -f3-
break
done
}
E ora che come oneliner:
tail -- "$(latest-file-in-directory)"
Se tutto il resto fallisce, puoi includere la funzione sopra nella tua .bashrce considerare il problema risolto, con un avvertimento. Se volevi semplicemente portare a termine il lavoro, non devi leggere altro.
L'avvertenza è che un nome di file che termina con una o più nuove righe non verrà ancora passato tailcorrettamente. Risolvere questo problema è complicato e ritengo sufficiente che, se si incontra un nome di file così dannoso, si verificherà il comportamento relativamente sicuro di riscontrare un errore "Nessun file di questo tipo" invece di qualcosa di più pericoloso.
Per i curiosi questa è la noiosa spiegazione di come funziona, perché è sicura e perché probabilmente altri metodi non lo sono.
Prima di tutto, l'unico byte sicuro per delimitare i percorsi dei file è nullo perché è l'unico byte universalmente proibito nei percorsi dei file sui sistemi Unix. È importante quando si gestisce un elenco di percorsi di file utilizzare null solo come delimitatore e, quando si passa anche un singolo percorso di file da un programma a un altro, farlo in modo da non soffocare su byte arbitrari. Esistono molti modi apparentemente corretti per risolvere questo e altri problemi che falliscono supponendo (anche accidentalmente) che i nomi dei file non contengano né nuove righe né spazi. Nessuna ipotesi è sicura.
Per gli scopi odierni il primo passo è quello di ottenere un elenco di file delimitato da null da trovare. Questo è abbastanza semplice se hai un findsupporto -print0come GNU:
find . -print0
Ma questo elenco non ci dice ancora quale è il più recente, quindi è necessario includere tali informazioni. Ho scelto di utilizzare l' -printfopzione find che mi consente di specificare quali dati vengono visualizzati nell'output. Non tutte le versioni di findsupporto -printf(non è standard) ma GNU find lo fa. Se ti trovi senza di -printfte, dovrai fare affidamento su -exec stat {} \;quale punto devi rinunciare a tutte le speranze di portabilità, dato che statnon è neanche standard. Per ora continuerò supponendo che tu abbia gli strumenti GNU.
find . -printf '%T@.%p\0'
Qui sto chiedendo il formato printf %T@che è il tempo di modifica in secondi dall'inizio dell'epoca Unix seguito da un punto e quindi seguito da un numero che indica le frazioni di secondo. Aggiungo a questo un altro periodo e poi %p(che è il percorso completo del file) prima di terminare con un byte null.
Adesso ho
find . -maxdepth 1 \! -type d -printf '%T@.%p\0'
Inutile dire che, per motivi di completezza, -maxdepth 1impedisce finddi elencare i contenuti delle sottodirectory e \! -type dsalta le directory che difficilmente si vorrebbe tail. Finora ho i file nella directory corrente con le informazioni sul tempo di modifica, quindi ora ho bisogno di ordinare per quel tempo di modifica.
Per impostazione predefinita, si sortaspetta che i suoi input siano record delimitati da newline. Se hai GNU sortpuoi chiedergli di aspettarti record delimitati da null usando invece l' -zopzione .; per standard sortnon esiste soluzione. Sono interessato solo all'ordinamento per i primi due numeri (secondi e frazioni di secondo) e non voglio ordinare in base al nome del file effettivo, quindi dico sortdue cose: in primo luogo, che dovrebbe considerare il punto ( .) un delimitatore di campo e secondo che dovrebbe usare solo il primo e il secondo campo quando si considera come ordinare i record.
| sort -znr -t. -k1,2
Prima di tutto, sto raggruppando tre opzioni brevi che non hanno valore insieme; -znrè solo un modo conciso di dire -z -n -r). Successivamente -t .(lo spazio è facoltativo) indica sortil carattere delimitatore di campo e -k 1,2specifica i numeri di campo: primo e secondo ( sortconta i campi da uno, non zero). Ricorda che un record di esempio per la directory corrente sarebbe simile a:
1000000000.0000000000../some-file-name
Questo significa sortche guarderemo prima 1000000000e poi 0000000000quando ordiniamo questo record. L' -nopzione dice sortdi usare il confronto numerico quando si confrontano questi valori, perché entrambi i valori sono numeri. Questo potrebbe non essere importante poiché i numeri hanno una lunghezza fissa ma non fa male.
L'altro interruttore dato sortè -rper "reverse". Per impostazione predefinita, l'output di un ordinamento numerico sarà prima il numero più basso, lo -rmodifica in modo tale da elencare per primi i numeri più bassi e quelli più alti. Dal momento che questi numeri sono più alti, i timestamp significheranno più recenti e questo metterà il record più recente all'inizio dell'elenco.
Poiché l'elenco dei percorsi dei file emerge da sortesso ora ha la risposta desiderata che stiamo cercando proprio in alto. Ciò che resta è trovare il modo di scartare gli altri record e di eliminare il timestamp. Sfortunatamente anche GNU heade tailnon accettano switch per farli funzionare su input delimitati da null. Invece uso un ciclo while come una specie di povero head.
| while IFS= read -r -d '' record
Innanzitutto ho disattivato in IFSmodo che l'elenco dei file non sia soggetto a suddivisione in parole. Successivamente dico readdue cose: non interpretare le sequenze di escape nell'input ( -r) e l'input è delimitato da un byte null ( -d); qui la stringa vuota ''viene utilizzata per indicare "nessun delimitatore" alias delimitato da null. Ogni record verrà letto nella variabile in recordmodo che ogni volta che il whileciclo itera abbia un singolo timestamp e un singolo nome di file. Nota che -dè un'estensione GNU; se hai solo uno standard readquesta tecnica non funzionerà e avrai poca possibilità.
Sappiamo che la recordvariabile ha tre parti, tutte delimitate da caratteri punto. Utilizzando l' cututilità è possibile estrarne una parte.
printf '%s' "$record" | cut -d. -f3-
Qui l'intero record viene passato printfe inviato da lì a cut; in bash si potrebbe semplificare ulteriormente questo utilizzando una stringa di qui per cut -d. -3f- <<<"$record"per migliorare le prestazioni. Diciamo cutdue cose: prima di -dtutto dovrebbe essere un delimitatore specifico per identificare i campi (come nel caso sortdel delimitatore .). Al secondo cutviene -frichiesto di stampare solo valori da campi specifici; l'elenco dei campi viene fornito come un intervallo 3-che indica il valore dal terzo campo e da tutti i campi seguenti. Ciò significa che cutleggerà e ignorerà tutto, incluso il secondo .che trova nel record, quindi stamperà il resto, che è la parte del percorso del file.
Dopo aver stampato il percorso del file più recente, non è necessario andare avanti: breakesce dal ciclo senza lasciarlo passare al secondo percorso del file.
L'unica cosa che rimane è in esecuzione tailsul percorso del file restituito da questa pipeline. Potresti aver notato nel mio esempio che l'ho fatto racchiudendo la pipeline in una subshell; quello che potresti non aver notato è che ho racchiuso la subshell tra virgolette doppie. Questo è importante perché alla fine, anche con tutto questo sforzo per essere al sicuro per qualsiasi nome di file, un'espansione della sottoshell non quotata potrebbe ancora rompere le cose. Una spiegazione più dettagliata è disponibile se sei interessato. Il secondo aspetto importante ma facilmente trascurabile dell'invocazione di tailè che gli ho fornito l'opzione --prima di espandere il nome del file. Questo istruiràtailche non vengono specificate più opzioni e tutto ciò che segue è un nome file, il che rende sicuro gestire i nomi dei file che iniziano con -.
tail -f $(find . -type f -exec stat -f "%m {}" {} \;| sort -n | tail -n 1 | cut -d ' ' -f 2)
heade tailnon accettano switch per farli funzionare su input delimitati da null." La mia sostituzione head: … | grep -zm <number> "".
tail `ls -t | head -1`
Se sei preoccupato per i nomi di file con spazi,
tail "`ls -t | head -1`"
Puoi usare:
tail $(ls -1t | head -1)
Il $()costrutto avvia una sotto-shell che esegue il comando ls -1t(elencando tutti i file in ordine di tempo, uno per riga) e eseguendo il piping attraverso head -1per ottenere la prima riga (file).
L'output di quel comando (il file più recente) viene quindi passato tailper essere elaborato.
Tieni presente che questo corre il rischio di ottenere una directory se questa è la voce di directory più recente creata. Ho usato quel trucco in un alias per modificare il file di registro più recente (da un set rotante) in una directory che conteneva solo quei file di registro.
-1è necessario, lo lsfa per te quando è in una pipa. Confronta lse ls|cat, per esempio.
Sui sistemi POSIX, non è possibile ottenere la voce della directory "ultima creazione". Ogni voce della directory ha atime, mtimee ctime, contrariamente a Microsoft Windows, ctimenon significa CreationTime, ma "Ora dell'ultimo cambio di stato".
Quindi il meglio che puoi ottenere è "accodare l'ultimo file modificato di recente", che è spiegato nelle altre risposte. Vorrei andare per questo comando:
tail -f "$ (ls -tr | sed 1q)"
Nota le virgolette attorno al lscomando. Questo fa funzionare lo snippet con quasi tutti i nomi di file.
In zsh:
tail *(.om[1])
Vedi: http://zsh.sourceforge.net/Doc/Release/Expansion.html#Glob-Qualifiers , qui mindica il tempo di modifica m[Mwhms][-|+]ne il precedente osignifica che è ordinato in un modo (lo Oordina nell'altro modo). I .mezzi solo i file regolari. Tra parentesi [1]seleziona il primo elemento. Per scegliere tre usi [1,3], per ottenere l'uso più vecchio [-1].
È bello in breve e non usa ls.
Probabilmente ci sono milioni di modi per farlo, ma il modo in cui lo farei è questo:
tail `ls -t | head -n 1`
I bit tra i backtick (la virgoletta come i caratteri) vengono interpretati e il risultato restituito alla coda.
ls -t #gets the list of files in time order
head -n 1 # returns the first line only
Un semplice:
tail -f /path/to/directory/*
funziona bene per me.
Il problema è ottenere file che vengono generati dopo aver avviato il comando tail. Ma se non ne hai bisogno (dal momento che tutte le soluzioni precedenti non se ne occupano), l'asterisco è solo una soluzione più semplice, IMO.
Qualcuno l'ha pubblicato e poi lo ha cancellato per qualche motivo, ma questo è l'unico che funziona, quindi ...
tail -f `ls -tr | tail`
lsnon è la cosa più intelligente da fare ...
tail -f `ls -lt | grep -v ^d | head -2 | tail -1 | tr -s " " | cut -f 8 -d " "`
Spiegazione: