Jak uzyskać tekst z zakresu dat za pomocą grep / sed w dużym pliku tekstowym?

9

Mam duży plik tekstowy (prawie 3 GB) - jest to plik dziennika. Chcę uzyskać wiersze tekstu odpowiadające zakresowi dat z tego pliku, od 13 lipca do 19 lipca. Mój format dziennika to:

2016-07-12 < ?xml version>
2016-07-13 < ?xml version>
2016-07-18 < ?xml version>
2016-07-18 < ?xml version>
2016-07-19 < ?xml version>
2016-07-20 < ?xml version>
sample text sample text
sample text sample text
sample text sample text
2016-07-20 < ?xml version>
sample text sample text
2016-07-20 < ?xml version>

więc po grep/ sedpowinno być tak wyprowadzone:

2016-07-13 < ?xml version>
2016-07-18 < ?xml version>
2016-07-18 < ?xml version>
2016-07-19 < ?xml version>

Jak mogę to zdobyć?

corey
źródło
2
Czy na pewno masz na myśli czerwiec ? Wszystkie daty w przykładowym pliku dziennika są w lipcu, a pożądana próbka wyjściowa sugeruje, że miałeś na myśli to drugie.
David Foerster,

Odpowiedzi:

13

Ze grepjeśli znasz numer linii chcesz, możesz użyć opcji kontekstowe -Adrukować wiersze na wzór

grep -A 3 2016-07-13 file

dzięki czemu uzyskasz linię z 2013-07-13 i następnymi 3 liniami

z sedmożesz użyć dat do takiego ograniczenia

sed -n '/2016-07-13/,/2016-07-19/p' file

która wydrukuje wszystkie linie od pierwszej linii z 2016-07-13 do pierwszej linii włącznie z 2016-07-19. Ale to zakłada, że ​​masz tylko jedną linię z 2016-07-19 (nie wydrukuje następnej linii). Jeśli jest wiele wierszy, użyj następnej daty i użyj, daby usunąć z niej wynik

sed -n '/2016-07-13/,/2016-07-20/{/2016-07-20/d; p}' file
Zanna
źródło
4

awk rozwiązanie:

$ awk '/^2016-07-13.*/,/2016-07-19.*/'  input.txt                                   
2016-07-13 < ?xml version> 
2016-07-18 < ?xml version> 
2016-07-18 < ?xml version> 
2016-07-19 < ?xml version> 

Zasadniczo drukuje każdą linię od tej, która zaczyna się od, 2016-07-13do tej, która zaczyna się od2016-07-19

Sergiy Kolodyazhnyy
źródło
4

Wszystkie pozostałe aktualne odpowiedzi opierają się na tym, że wpisy w pliku dziennika są sortowane chronologicznie lub na tym, że zakres dat można łatwo dopasować za pomocą wyrażeń regularnych. Jeśli chcesz bardziej ogólnego rozwiązania, musimy zrobić trochę więcej programowania.

Przedstawiam ten skrypt GNU AWK:

#!/usr/bin/gawk -f
BEGIN {
    starttime = mktime(starttime)
    endtime = mktime(endtime)
}

func in_range(n, start, end) {
    return start <= n && n < end
}

match($0, /^([0-9]{4})-([0-9]{2})-([0-9]{2})\s/, m) &&
    in_range(mktime(m[1] " " m[2] " " m[3] " 00 00 00"), starttime, endtime)

Czas rozpoczęcia i zakończenia podajesz za pomocą zmiennych starttimeoraz endtimew formacie, który mktimeobsługuje ( YYYY MM DD hh dd ss). W ten sposób uruchamiasz awkpolecenie w ten sposób, zakładając, że powyższy skrypt Awk znajduje się w pliku wykonywalnym filter-log-dates.awkw bieżącym katalogu roboczym, a plik dziennika to mylog.txt:

./filter-log-dates.awk -v starttime='2016 07 13 00 00 00' -v endtime='2016 07 20 00 00 00' mylog.txt

Należy pamiętać, że czas zakończenia jest wyłączny , tj. Prawidłowe rekordy dziennika muszą mieć znacznik czasu przed czasem zakończenia.

Jeśli format znacznika czasu jest inny, możesz dostosować wyrażenie regularne przekazane do matchfunkcji, aby go dopasować.

David Foerster
źródło
3

Możesz to zrobić w kilku krokach. Znajdź numer pierwszego wiersza pasującego do wzorca początkowego. Znajdź numer ostatniej linii pasującej do wzorca końcowego. Następnie wyodrębnij test między tymi dwiema liniami. Można to zrobić w następujący sposób.

grep -n 2016-07-13 bigtextfile | head -1
grep -n 2016-07-19 bigtestfile | tail -1
# Say the first number is 1234 and the second 5678, then use...
awk 'NR>=1234 && NR<=5678' bigtestfile > rangeoftext

Można to zrobić wszystko za pomocą awkpolecenia, ale kroki mogą ułatwić wykonanie. W awk zmienna NR jest bieżącym numerem linii, a ponieważ po wzorcu nie określono żadnej akcji (NR> = 1234 && NR <= 5678), domyślną akcją jest wydrukowanie linii z tego zakresu.

Jeffrey Ross
źródło