Znajdź ostatnie wystąpienie ciągu w wielu plikach

9

Muszę przeszukać wiele plików dziennika (wszystkie pliki wygenerowane w ciągu ostatnich 24 godzin, wszystkie przechowywane w tym samym katalogu), aby znaleźć ostatnie wystąpienie ciągu. Oto polecenie, które napisałem:

find . -mtime 1 | grep fileprefix | xargs grep 'search string' | tail -1

Ale to zwraca tylko ostatnią linię dla jednego pliku. Wszelkie sugestie, jak to zmienić, aby uzyskać wszystkie linie?

Lokesh
źródło
próbowałeś odwrócić ogon i ostatnie grep? odnaleźć . -mtime 1 | grep fileprefix | xargs tail -1 | grep 'search string'
Mathieu,

Odpowiedzi:

4

Zakładając, że obiekty GNU:

find . -mtime -1 -exec bash -c \
'for f; do tac "$f" | grep -m1 fileprefix; done' _ {} +
iruvar
źródło
Czy możesz proszę opracować cel „bash -c \”, ponieważ już używam powłoki bash. Również cel „_ {} +” na końcu.
Lokesh,
@Lokesh, możesz dostać się finddo wykonywania poleceń na plikach za pomocą -exec. Dzięki bash -c, jesteśmy tarła bashskorupę że pętle za pośrednictwem plików znaleźć findi wykonuje tac .. | grep -m1 fileprefixna każdym
Iruvar
Próbowałem rozszerzyć filtrowanie ciągu znaków w pętli za pomocą polecenia cut tj. Dla f; do tac "$ f" | grep -m1 poprawka pliku | cut -d '' -f4,7-8, ale gdy wstawię polecenie cut, daje mi błąd nieoczekiwany koniec pliku. Czy możesz mi zasugerować, co robię źle.
Lokesh,
@lokesh, użyj -d" "z cięciem. Podwójne cudzysłowy zamiast pojedynczego
iruvar
1
findPolecenia można filtrować za prefiksu pliku; grepnie powinno być potrzebne do tego. Zaskakujące jest również to, że szukany ciąg nie figuruje w tej odpowiedzi.
Jonathan Leffler,
8

Jeśli wszystko jest w jednym katalogu, możesz:

for file in *fileprefix*; do
    grep 'search string' "$file" | tail -1
done

Jeśli są to duże pliki, warto przyspieszyć, tacdrukując plik w odwrotnej kolejności (ostatni wiersz), a następnie grep -m1dopasowując do pierwszego wystąpienia. W ten sposób unikniesz konieczności czytania całego pliku:

for file in *fileprefix*; do
    tac file | grep -m1 'search string'
done

Oba zakładają, że nie ma pasujących katalogów fileprefix. Jeśli tak, pojawi się błąd, który możesz po prostu zignorować. Jeśli to jest problem, sprawdź tylko pliki:

 for file in *fileprefix*; do
    [ -f "$file" ] && tac file | grep -m1 'search string'
 done

Jeśli potrzebujesz także wydrukować nazwę pliku, dodaj -Hdo każdego grepwywołania. Lub, jeśli grepnie obsługuje tego, powiedz mu, aby przeszukać /dev/null. To nie zmieni danych wyjściowych, ale ponieważ greppodano wiele plików, zawsze będzie drukować nazwę pliku dla każdego trafienia:

for file in *fileprefix*; do
    grep 'search string' "$file" /dev/null | tail -1
done
terdon
źródło
„W ten sposób unikasz konieczności czytania całego pliku” - co? Nie, unikasz czytania całego pliku w grep, ale zamiast tego wkładasz cały plik przez tac. Nie jest dla mnie jasne, czy byłoby to szybsze, choć zależałoby to od tego, czy dopasowanie było blisko początku czy końca pliku.
Gilles „SO- przestań być zły”
@Gilles nie, nie przejdziesz też całego pliku tac. Wyjdzie, gdy tylko pierwszy mecz zostanie znaleziony. Właśnie przetestowałem z plikiem tekstowym 832M i wzorem znalezionym w ostatnim wierszu. grep -m 1 pattern filenarzędzie ~ 7 sekund i tac file | grep -m1 patternzajęło 0.009.
terdon
4
find . ! -name . -prune -mtime 1 -name 'fileprefix*' \
     -exec sed -se'/searchstring/h;$!d;x' {} +

... będzie działać, jeśli masz GNU, sedktóry obsługuje -sopcję oddzielnych plików i POSIX find.

Prawdopodobnie powinieneś jednak dodać ! -type dlub -type fkwalifikatory, ponieważ próba odczytania katalogu nie będzie bardzo przydatna, a dalsze zawężenie zakresu do zwykłych plików może zapobiec zawieszeniu odczytu na potoku lub pliku urządzenia szeregowego.

Logika jest niezwykle prosta - sedzastępuje swoją hstarą przestrzeń kopią dowolnego pasującego wiersza wejściowego searchstring, a następnie dusuwa z wyjścia wszystkie wiersze wejściowe, ale ostatnie dla każdego pliku wejściowego. Gdy dojdzie do ostatniego wiersza, xzmienia przestrzenie wstrzymania i wzorców, więc jeśli searchstringw ogóle zostanie znalezione podczas odczytu pliku, ostatnie takie wystąpienie zostanie automatycznie wydrukowane na wyjście, w przeciwnym razie zapisze pustą linię. (dodaj /./!dto do końca sedskryptu, jeśli jest to niepożądane) .

Spowoduje to wykonanie pojedynczego sedwywołania dla około 65 000 plików wejściowych - lub dowolnego ARG_MAXlimitu. To powinno być bardzo wydajne rozwiązanie i jest po prostu zaimplementowane.

Jeśli chcesz także nazwy plików, biorąc pod uwagę najnowszy GNU sed, możesz zapisać je w osobnych wierszach za pomocą Fpolecenia, albo możesz wydrukować je findna osobnej liście dla każdej partii, dodając -printgłówną po +.

mikeserv
źródło
1

Co powiesz na:

find . -mtime -1 -name "fileprefix*" -exec sh -c \
'echo "$(grep 'search string' $1 | tail -n 1),$1"' _ {} \;

Powyższe daje ładne wyjście z ostatnim wystąpieniem ciągu wyszukiwania w każdym pliku, po którym następuje nazwa pliku po przecinku (zmodyfikuj część „, $ 1” pod echem, aby zmienić formatowanie lub usunąć, jeśli nie jest to konieczne). Przykładowe dane wyjściowe, które wyszukują ciąg „10” w plikach z prefiksem nazwy „plik”, są następujące:

[dmitry@localhost sourceDir]$ find . -mtime -1 -name "file*" -exec  sh -c 'echo "$(grep '10' $1 | tail -n 1),$1"' _ {} \;
Another data 02 10,./file02.log
Some data 01 10,./file01.log
Yet another data 03 10,./file03.log 
Dmitry Aleks
źródło
1
find . -mtime 1 -name 'fileprefix*' -exec grep -Hn 'search string' {} + |
    sort -t: -k1,2 -n | 
    awk -F: '{key=$1 ; $1="" ; $2="" ; gsub(/^  /,"",$0); a[key]=$0} 
             END {for (key in a) { print key ":" a[key] }}'

Wykorzystuje GNU grep„s -Hi -nopcje, aby zawsze wydrukować zarówno nazwę pliku i LineNumber wszystkich meczów, następnie sortuje według nazwy pliku i LineNumber i rury go do awk, który przechowuje ostatni mecz dla każdego pliku w tablicy, a ostatecznie drukuje to.

Metoda dość brutalna, ale działa.

cas
źródło