jak „ogonić” najnowszy plik w katalogu

20

W powłoce, jak mogę tailnajnowszy plik utworzony w katalogu?

Itay Moav-Malimovka
źródło
1
przyjdźcie bliżej, programiści muszą ogonić!
amit
Zamknięcie służy tylko do przejścia do superużytkownika lub błędu serwera. Pytanie będzie tam mieszkało i znajdzie je więcej osób, które mogą być zainteresowane.
Mnementh
Prawdziwym problemem tutaj jest znalezienie najnowszego pliku aktualizacji w katalogu i uważam, że już na nie odpowiedziano (ani tutaj, ani na Super User, nie pamiętam).
dmckee,

Odpowiedzi:

24

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 .bashrci 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 tailpoprawnie 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.

Soczyste szczegóły

Dla ciekawskich jest to żmudne wyjaśnienie, jak to działa, dlaczego jest bezpieczny i dlaczego inne metody prawdopodobnie nie są.

Niebezpieczeństwo, Will Robinson

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 findwsparcie -print0takie 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 -printfprzełącznika find, który pozwala mi określić, jakie dane pojawiają się na wyjściu. Nie wszystkie wersje findwsparcia -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ż statnie 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 1uniemożliwia findwyświetlanie zawartości podkatalogów i \! -type dpomija 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.

Uzyskiwanie we właściwej kolejności

Domyślnie sortoczekuje, ż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 -zprzełącznika .; dla standardu sortnie 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ę sortdwie 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; -znrto tylko zwięzły sposób powiedzenia -z -n -r). Następnie -t .(spacja jest opcjonalna) podaje sortznak ogranicznika pola i -k 1,2określa numery pól: pierwszy i drugi ( sortzlicza 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, 1000000000a potem 0000000000zamówisz ten rekord. Ta -nopcja mówi sorto 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 sortsłuży -rdo „wstecz”. Domyślnie wynikiem sortowania liczbowego będą najpierw liczby najniższe, -rzmienia 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.

Tylko ważne bity

Gdy wyłania się z sortniej 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 headi tailnie 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, IFSaby lista plików nie była dzielona na słowa. Następnie powiem readdwie 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, recorddzięki czemu przy każdej whileiteracji pętli będzie miał jeden znacznik czasu i jedną nazwę pliku. Zauważ, że -djest to rozszerzenie GNU; jeśli masz tylko standard, readta technika nie zadziała i nie będziesz mieć możliwości skorzystania z niej.

Wiemy, że recordzmienna składa się z trzech części, wszystkie rozdzielone znakami kropki. Za pomocą cutnarzędzia można wyodrębnić ich część.

printf '%s' "$record" | cut -d. -f3-

Tutaj cały rekord jest przekazywany do printfi 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 cutdwie rzeczy: Po pierwsze -d, powinien to być określony separator do identyfikowania pól (tak jak w sortprzypadku separatora .). Drugie cutpolecenie -fjest 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 cutprzeczyta 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ć: breakwychodzi 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 tailjest 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 -.

phogg
źródło
1
@AakashM: ponieważ możesz otrzymać „zaskakujące” wyniki, np. Jeśli nazwa pliku zawiera „niezwykłe” znaki (prawie wszystkie znaki są zgodne z prawem).
John Zwinck,
6
Ludzie, którzy używają znaków specjalnych w nazwach plików, zasługują na wszystko, co otrzymują :-)
6
Widok paxdiablo sprawił, że ta uwaga była wystarczająco bolesna, ale potem dwie osoby zagłosowały! Ludzie, którzy piszą błędne oprogramowanie, celowo zasługują na wszystko, co otrzymują.
John Zwinck,
4
Tak więc powyższe rozwiązanie nie działa na osx z powodu braku opcji -printf w find, ale następujące działa tylko na osx z powodu różnic w poleceniu stat ... może nadal komuś pomożetail -f $(find . -type f -exec stat -f "%m {}" {} \;| sort -n | tail -n 1 | cut -d ' ' -f 2)
audio.zoom
2
„Niestety, nawet GNU headi tailnie akceptują przełączników, aby działały na danych rozdzielanych znakiem zerowym”. Mój zamiennik head: … | grep -zm <number> "".
Kamil Maciorowski
22
tail `ls -t | head -1`

Jeśli martwisz się nazwami plików ze spacjami,

tail "`ls -t | head -1`"
Pointy
źródło
1
Ale co się stanie, gdy twój najnowszy plik ma spacje lub znaki specjalne? Użyj $ () zamiast `` i cytuj swoją podpowłokę, aby uniknąć tego problemu.
phogg
Lubię to. Czysty i prosty. Tak jak powinno być.
6
Łatwo jest być czystym i prostym, jeśli poświęcisz solidnego i poprawnego.
phogg
2
To zależy od tego, co robisz, naprawdę. Rozwiązanie, które zawsze działa wszędzie, dla wszystkich możliwych nazw plików, jest bardzo miłe, ale w ograniczonej sytuacji (na przykład pliki dziennika o znanych nietypowych nazwach) może być niepotrzebne.
To jak dotąd najczystsze rozwiązanie. Dziękuję Ci!
demisx
4

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 -1uzyskać pierwszą linię (plik).

Dane wyjściowe tego polecenia (najnowszego pliku) są następnie przekazywane do tailprzetworzenia.

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.


źródło
To -1nie jest konieczne, lsrobi to dla ciebie, gdy jest w rurze. Porównaj lsi ls|catna przykład.
Wstrzymano do odwołania.
Może tak być w przypadku Linuksa. W „prawdziwym” Uniksie procesy nie zmieniły swojego zachowania w zależności od tego, dokąd zmierzały dane wyjściowe. To sprawiłoby, że debugowanie potoku było naprawdę denerwujące :-)
Hmmm, nie jestem pewien, czy to poprawne - ISTR musi wydać „ls -C”, aby uzyskać wyjście sformatowane w kolumnie pod 4.2BSD podczas przesyłania danych wyjściowych przez filtr, i jestem prawie pewien, że ls pod Solaris działa w ten sam sposób. Co to właściwie jest „jeden, prawdziwy Unix”?
Cytaty! Cytaty! W nazwach plików są spacje!
Norman Ramsey,
@TMN: Jedynym prawdziwym sposobem uniksowym jest nie poleganie na ls dla nie-ludzkich konsumentów. „Jeśli dane wyjściowe dotyczą terminala, format jest zdefiniowany w implementacji.” - to jest specyfikacja. Jeśli chcesz być pewien, musisz powiedzieć ls -1 lub ls -C.
phogg
4

W systemach POSIX nie ma możliwości uzyskania pozycji katalogu „ostatnio utworzony”. Każda pozycja katalogu ma atime, mtimei ctime, ale w przeciwieństwie do systemu Microsoft Windows, ctimenie 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ół lspolecenia. Dzięki temu fragment kodu działa z prawie wszystkimi nazwami plików.

Roland Illig
źródło
Dobra robota. Od razu do rzeczy. +1
Norman Ramsey
4

Chcę tylko zobaczyć zmianę rozmiaru pliku, którego można użyć zegarka.

watch -d ls -l
tpal
źródło
3

W zsh:

tail *(.om[1])

Zobacz: http://zsh.sourceforge.net/Doc/Release/Expansion.html#Glob-Qualifiers , tutaj moznacza czas modyfikacji m[Mwhms][-|+]n, a powyższy ooznacza, że ​​jest on sortowany w jeden sposób ( Osortuje 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.

Anne van Rossum
źródło
1

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
iblamefish
źródło
2
Backticks są złe. Zamiast tego użyj $ ().
William Pursell
1

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.

bruno.braga
źródło
0
tail`ls -tr | tail -1`
użytkownik22644
źródło
Zapomniałeś miejsca!
Blacklight Shining
0

Ktoś to opublikował, a następnie z jakiegoś powodu usunął, ale to jedyny, który działa, więc ...

tail -f `ls -tr | tail`
Itay Moav-Malimovka
źródło
musisz wykluczyć katalogi, prawda?
amit
1
Opublikowałem to pierwotnie, ale usunąłem, ponieważ zgadzam się z Sorpigalem, że analizowanie danych wyjściowych z lsnie jest najmądrzejszą rzeczą do zrobienia ...
ChristopheD
Potrzebuję go szybko i brudno, bez katalogów. Więc jeśli dodasz swoją odpowiedź, zaakceptuję tę
Itay Moav -Malimovka
0
tail -f `ls -lt | grep -v ^d | head -2 | tail -1 | tr -s " " | cut -f 8 -d " "`

Wyjaśnienie:

  • ls -lt: Lista wszystkich plików i katalogów posortowanych według czasu modyfikacji
  • grep -v ^ d: wyklucza katalogi
  • głowa -2 i dalej: analizowanie potrzebnej nazwy pliku
amit
źródło
1
+1 za sprytne, -2 za parsowanie wyjścia ls, -1 za brak cytowania podpowłoki, -1 za magiczne założenie „pola 8” (nie jest przenośne!) I na koniec -1 za zbyt sprytne . Ogólna ocena: -4.
phogg
@Sorpigal Agreed. Ale szczęśliwy, że jestem złym przykładem.
amit
tak, nie wyobrażałem sobie, że byłoby tak źle pod wieloma
względami
0
tail "$(ls -1tr|tail -1)"
użytkownik31894
źródło