W powłoce, jak mogę tail
najnowszy plik utworzony w katalogu?
W powłoce, jak mogę tail
najnowszy plik utworzony w katalogu?
Odpowiedzi:
Czy nie analizować wyjście ls! Analiza składni ls jest trudna i zawodna .
Jeśli musisz to zrobić, polecam użycie find. Pierwotnie miałem tutaj prosty przykład, aby dać sedno rozwiązania, ale ponieważ ta odpowiedź wydaje się dość popularna, postanowiłem ją poprawić, aby zapewnić wersję, którą można bezpiecznie skopiować / wkleić i używać ze wszystkimi danymi wejściowymi. Czy siedzisz wygodnie? Zaczniemy od oneliner, który da ci najnowszy plik w bieżącym katalogu:
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)"
Czyż nie jest to już oneliner? Tutaj znów jest to funkcja powłoki i sformatowana dla łatwiejszego czytania:
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
}
I teraz , że jako oneliner:
tail -- "$(latest-file-in-directory)"
Jeśli wszystko inne zawiedzie, możesz dołączyć powyższą funkcję do swojego .bashrc
i rozważyć problem rozwiązany, z jednym zastrzeżeniem. Jeśli chcesz po prostu wykonać zadanie, nie musisz czytać dalej.
Zastrzeżenie polega na tym, że nazwa pliku kończąca się na co najmniej jednym nowym wierszu nadal nie będzie tail
poprawnie przekazywana . Obejście tego problemu jest skomplikowane i uważam za wystarczające, że w przypadku napotkania takiej złośliwej nazwy pliku wystąpi względnie bezpieczne zachowanie błędu „Brak takiego pliku” zamiast czegoś bardziej niebezpiecznego.
Dla ciekawskich jest to żmudne wyjaśnienie, jak to działa, dlaczego jest bezpieczny i dlaczego inne metody prawdopodobnie nie są.
Po pierwsze, jedynym bajtem, który można bezpiecznie wytyczyć ścieżki do plików, jest null, ponieważ jest to jedyny bajt powszechnie zabroniony w ścieżkach plików w systemach uniksowych. Ważne jest, aby podczas obsługi dowolnej listy ścieżek plików używać ogranicznika null jako ogranicznika, a także w przypadku przekazywania nawet jednej ścieżki pliku z jednego programu do drugiego, aby robić to w sposób, który nie będzie dławił się na dowolnych bajtach. Istnieje wiele pozornie poprawnych sposobów rozwiązania tego i innych problemów, które zawodzą, zakładając (nawet przypadkowo), że nazwy plików nie będą miały ani nowych linii, ani spacji. Żadne z tych założeń nie jest bezpieczne.
Dla dzisiejszych celów pierwszym krokiem jest uzyskanie listy plików rozdzielonych znakiem null. Jest to dość łatwe, jeśli masz find
wsparcie -print0
takie jak GNU:
find . -print0
Ale ta lista wciąż nie mówi nam, która z nich jest najnowsza, dlatego musimy podać te informacje. Wybieram użycie -printf
przełącznika find, który pozwala mi określić, jakie dane pojawiają się na wyjściu. Nie wszystkie wersje find
wsparcia -printf
(to nie jest standardowe), ale GNU find tak. Jeśli znajdziesz się bez niego -printf
, będziesz musiał polegać na tym, -exec stat {} \;
w którym momencie musisz zrezygnować z wszelkiej nadziei na przenośność, co również stat
nie jest standardem. Na razie zamierzam przejść dalej, zakładając, że masz narzędzia GNU.
find . -printf '%T@.%p\0'
Tutaj pytam o format printf, %T@
czyli czas modyfikacji w sekundach od początku epoki Uniksa, po którym następuje kropka, a następnie liczba wskazująca ułamki sekundy. Dodaję do tego kolejny okres, a następnie %p
(czyli pełną ścieżkę do pliku) przed zakończeniem bajtem zerowym.
Teraz mam
find . -maxdepth 1 \! -type d -printf '%T@.%p\0'
Może to być oczywiste, ale ze względu na kompletność -maxdepth 1
uniemożliwia find
wyświetlanie zawartości podkatalogów i \! -type d
pomija katalogi, których raczej nie chcesz tail
. Do tej pory mam pliki w bieżącym katalogu z informacjami o czasie modyfikacji, więc teraz muszę posortować według czasu modyfikacji.
Domyślnie sort
oczekuje, że jego dane wejściowe będą rekordami rozdzielanymi znakiem nowej linii. Jeśli masz GNU sort
, możesz poprosić go, aby zamiast tego oczekiwał rekordów rozdzielanych zerami, używając -z
przełącznika .; dla standardu sort
nie ma rozwiązania. Interesuje mnie tylko sortowanie według pierwszych dwóch liczb (sekund i ułamków sekundy) i nie chcę sortować według rzeczywistej nazwy pliku, więc mówię sort
dwie rzeczy: po pierwsze, że powinien uwzględniać kropkę ( .
) jako ogranicznik pola a po drugie, powinno używać tylko pierwszego i drugiego pola podczas rozważania sposobu sortowania rekordów.
| sort -znr -t. -k1,2
Przede wszystkim łączę trzy krótkie opcje, które nie biorą razem żadnej wartości; -znr
to tylko zwięzły sposób powiedzenia -z -n -r
). Następnie -t .
(spacja jest opcjonalna) podaje sort
znak ogranicznika pola i -k 1,2
określa numery pól: pierwszy i drugi ( sort
zlicza pola od jednego, a nie zero). Pamiętaj, że przykładowy rekord dla bieżącego katalogu wyglądałby następująco:
1000000000.0000000000../some-file-name
Oznacza to sort
, że najpierw przejrzysz, 1000000000
a potem 0000000000
zamówisz ten rekord. Ta -n
opcja mówi sort
o użyciu porównania numerycznego przy porównywaniu tych wartości, ponieważ obie wartości są liczbami. Może to nie być ważne, ponieważ liczby mają ustaloną długość, ale nie szkodzi.
Drugi podany przełącznik sort
służy -r
do „wstecz”. Domyślnie wynikiem sortowania liczbowego będą najpierw liczby najniższe, -r
zmienia się tak, aby wyświetlały najniższe liczby jako ostatnie, a najwyższe jako pierwsze. Ponieważ te liczby są znacznikami czasu wyższe, będą oznaczały nowsze, a to stawia najnowszy rekord na początku listy.
Gdy wyłania się z sort
niej lista ścieżek do plików, na górze znajduje się żądana odpowiedź, której szukamy. Pozostaje znaleźć sposób na usunięcie innych rekordów i usunięcie znacznika czasu. Niestety nawet GNU head
i tail
nie akceptują przełączników, aby działały na danych rozdzielanych znakiem zerowym. Zamiast tego używam pętli while jako rodzaju biednego człowieka head
.
| while IFS= read -r -d '' record
Najpierw rozbroiłem, IFS
aby lista plików nie była dzielona na słowa. Następnie powiem read
dwie rzeczy: Nie interpretuj sekwencji ucieczki w input ( -r
), a dane wejściowe są rozdzielone bajtem zerowym ( -d
); tutaj pusty ciąg ''
jest używany do wskazania „no delimiter” aka delimited by null. Każdy rekord zostanie wczytany do zmiennej, record
dzięki czemu przy każdej while
iteracji pętli będzie miał jeden znacznik czasu i jedną nazwę pliku. Zauważ, że -d
jest to rozszerzenie GNU; jeśli masz tylko standard, read
ta technika nie zadziała i nie będziesz mieć możliwości skorzystania z niej.
Wiemy, że record
zmienna składa się z trzech części, wszystkie rozdzielone znakami kropki. Za pomocą cut
narzędzia można wyodrębnić ich część.
printf '%s' "$record" | cut -d. -f3-
Tutaj cały rekord jest przekazywany do printf
i stamtąd przesyłany do cut
; w bashu możesz to jeszcze bardziej uprościć, używając łańcucha tutaj, aby cut -d. -3f- <<<"$record"
uzyskać lepszą wydajność. Mówimy cut
dwie rzeczy: Po pierwsze -d
, powinien to być określony separator do identyfikowania pól (tak jak w sort
przypadku separatora .
). Drugie cut
polecenie -f
jest drukowane tylko wartości z określonych pól; lista pól jest podana jako zakres 3-
wskazujący wartość z trzeciego pola i ze wszystkich następujących pól. Oznacza to, że cut
przeczyta i zignoruje wszystko aż do sekundy .
, którą znajdzie w rekordzie, a następnie wydrukuje resztę, czyli ścieżkę do pliku.
Po wydrukowaniu najnowszej ścieżki do pliku nie trzeba kontynuować: break
wychodzi z pętli, nie pozwalając jej przejść do drugiej ścieżki do pliku.
Pozostaje tylko uruchomienie tail
ścieżki pliku zwróconej przez ten potok. Być może zauważyłeś w moim przykładzie, że zrobiłem to, umieszczając potok w podpowłoce; to, czego być może nie zauważyłeś, to to, że umieściłem podpowłokę w podwójnych cudzysłowach. Jest to ważne, ponieważ w końcu nawet przy tych wszystkich wysiłkach, aby być bezpiecznym dla nazw plików, niecytowane rozszerzenie podpowłoki może nadal zepsuć. Bardziej szczegółowe wyjaśnienie jest dostępna, jeśli jesteś zainteresowany. Drugim ważnym, ale łatwo przeoczanym aspektem wywołania tail
jest to, że podałem opcję --
przed rozwinięciem nazwy pliku. To pouczytail
że nie określono już żadnych opcji, a wszystko, co następuje, to nazwa pliku, dzięki czemu można bezpiecznie obsługiwać nazwy plików zaczynające się od -
.
tail -f $(find . -type f -exec stat -f "%m {}" {} \;| sort -n | tail -n 1 | cut -d ' ' -f 2)
head
i tail
nie akceptują przełączników, aby działały na danych rozdzielanych znakiem zerowym”. Mój zamiennik head
: … | grep -zm <number> ""
.
tail `ls -t | head -1`
Jeśli martwisz się nazwami plików ze spacjami,
tail "`ls -t | head -1`"
Możesz użyć:
tail $(ls -1t | head -1)
$()
Konstrukt rozpoczyna sub-shell, który uruchamia polecenie ls -1t
(z wyszczególnieniem wszystkich plików w porządku chronologicznym, po jednej w wierszu) i orurowanie że przez head -1
uzyskać pierwszą linię (plik).
Dane wyjściowe tego polecenia (najnowszego pliku) są następnie przekazywane do tail
przetworzenia.
Należy pamiętać, że grozi to uzyskaniem katalogu, jeśli jest to najnowszy utworzony wpis w katalogu. Użyłem tej sztuczki w aliasie, aby edytować najnowszy plik dziennika (z zestawu obrotowego) w katalogu, który zawierał tylko te pliki dziennika.
-1
nie jest konieczne, ls
robi to dla ciebie, gdy jest w rurze. Porównaj ls
i ls|cat
na przykład.
W systemach POSIX nie ma możliwości uzyskania pozycji katalogu „ostatnio utworzony”. Każda pozycja katalogu ma atime
, mtime
i ctime
, ale w przeciwieństwie do systemu Microsoft Windows, ctime
nie znaczy CreationTime, ale „czas ostatniej zmiany stanu”.
Więc najlepsze, co możesz uzyskać, to „ogonić ostatnio zmodyfikowany plik”, co wyjaśniono w innych odpowiedziach. Wybrałbym to polecenie:
tail -f "$ (ls -tr | sed 1q)"
Zwróć uwagę na cytaty wokół ls
polecenia. Dzięki temu fragment kodu działa z prawie wszystkimi nazwami plików.
W zsh
:
tail *(.om[1])
Zobacz: http://zsh.sourceforge.net/Doc/Release/Expansion.html#Glob-Qualifiers , tutaj m
oznacza czas modyfikacji m[Mwhms][-|+]n
, a powyższy o
oznacza, że jest on sortowany w jeden sposób ( O
sortuje w inny sposób). W .
jedynym środkiem zwykłe pliki. W nawiasach [1]
wybiera pierwszy element. Aby wybrać trzy użycie [1,3]
, aby uzyskać najstarsze użycie [-1]
.
Jest ładny krótki i nie używa ls
.
Prawdopodobnie jest na to milion sposobów, ale chciałbym to zrobić w następujący sposób:
tail `ls -t | head -n 1`
Bity między znakami odwrotnymi (znaki podobne do cudzysłowu) są interpretowane, a wynik wraca do końca.
ls -t #gets the list of files in time order
head -n 1 # returns the first line only
Prosty:
tail -f /path/to/directory/*
działa dobrze dla mnie.
Problemem jest uzyskanie plików, które są generowane po uruchomieniu polecenia tail. Ale jeśli nie potrzebujesz tego (ponieważ wszystkie powyższe rozwiązania nie dbają o to), gwiazdka jest po prostu prostszym rozwiązaniem, IMO.
Ktoś to opublikował, a następnie z jakiegoś powodu usunął, ale to jedyny, który działa, więc ...
tail -f `ls -tr | tail`
ls
nie jest najmądrzejszą rzeczą do zrobienia ...
tail -f `ls -lt | grep -v ^d | head -2 | tail -1 | tr -s " " | cut -f 8 -d " "`
Wyjaśnienie:
tail "$(ls -1tr|tail -1)"