2013-08-28 21 views
6

Czytałem wszystkie artykuły mogę znaleźć, nawet zrozumiałe, kilka z nich, ale jako NEWB Pythona nadal jestem trochę zagubiony i licząc na pomoc :)Wielu pasującej linii w Pythonie

I "Pracuję nad skryptem do analizowania interesujących rzeczy z pliku dziennika specyficznego dla aplikacji, każda linia zaczyna się od znacznika czasu, który mogę dopasować i mogę zdefiniować dwie rzeczy do zidentyfikowania tego, co chcę przechwycić, częściową zawartość i ciąg znaków to będzie zakończenie tego, co chcę wydobyć.

Mój problem jest wieloliniowy, w większości przypadków każda linia dziennika jest zakończona znakiem nowej linii, ale niektóre pozycje zawierają kod SQL, który może zawierać nowe wiersze i dlatego tworzy nowe linie w dzienniku.

więc w najprostszym przypadku może mam to:

[8/21/13 11:30:33:557 PDT] 00000488 SystemOut  O 21 Aug 2013 11:30:33:557 [WARN] [MXServerUI01] [CID-UIASYNC-17464] BMXAA6720W - USER = (ABCDEF) SPID = (2526) app (ITEM) object (ITEM) : select * from item where ((status != 'OBSOLETE' and itemsetid = 'ITEMSET1') and (exists (select 1 from maximo.invvendor where (exists (select 1 from maximo.companies where ((contains(name,' $AAAA ') > 0)) and (company=invvendor.manufacturer and orgid=invvendor.orgid))) and (itemnum = item.itemnum and itemsetid = item.itemsetid)))) and (itemtype in (select value from synonymdomain where domainid='ITEMTYPE' and maxvalue = 'ITEM')) order by itemnum asc (execution took 2083 milliseconds) 

To wszystko pojawia się jako jednej linii, które można dopasować z tym:

re.compile('\[(0?[1-9]|[12][0-9]|3[01])(\/)(0?[1-9]|[12][0-9]|3[01])(\/)([0-9]{2}).*(milliseconds)') 

Jednak w niektórych przypadkach może być linia łamie w SQL, jako taki chcę go nadal przechwytywać (i potencjalnie zastępować linie spacjami). Obecnie czytam ten plik w wierszu, co oczywiście nie zadziała, więc ...

  1. Czy muszę przetworzyć cały plik za jednym razem? Zwykle mają rozmiar 20 mb. Jak czytać cały plik i przeglądać go, szukając bloków jedno- lub wieloliniowych?
  2. Jak napisać wieloliniowy raport RegEx, który pasowałby do całej rzeczy w jednym wierszu lub byłby rozłożony na wiele linii?

Moim nadrzędnym celem jest parametryzacji to więc można go używać do wydobywania wpisy dziennika, które odpowiadają różne wzory napisu początkowego (zawsze na początku linii), ciąg kończąc (gdzie chcę uchwycić to) i wartość, która jest między nimi jako identyfikator.

Dzięki z góry za wszelką pomoc!

Chris.

import sys, getopt, os, re 

sourceFolder = 'C:/MaxLogs' 
logFileName = sourceFolder + "/Test.log" 
lines = [] 
print "--- START ----" 
lineStartsWith = re.compile('\[(0?[1-9]|[12][0-9]|3[01])(\/)(0?[1-9]|[12][0-9]|3[01])(\/)([0-9]{2})(\)') 
lineContains = re.compile('.*BMXAA6720W.*') 
lineEndsWith = re.compile('(?:.*milliseconds.*)') 

lines = [] 
with open(logFileName, 'r') as f: 
    for line in f: 
     if lineStartsWith.match(line) and lineContains.match(line): 
      if lineEndsWith.match(line) : 
       print 'Full Line Found' 
       print line 
       print "- Record Separator -" 
      else: 
       print 'Partial Line Found' 
       print line 
       print "- Record Separator -" 

print "--- DONE ----" 

Następny krok, dla mojej częściowej linii będę kontynuować czytanie aż znajdę lineEndsWith i zmontuję linie do jednego bloku.

Nie jestem ekspertem, więc sugestie są zawsze mile widziane!

AKTUALIZACJA - Tak więc pracuję, dzięki wszystkim odpowiedziom, które pomogły mi w kierowaniu, zdaję sobie sprawę, że to nie jest ładne i muszę posprzątać mój bałagan/elif i sprawić, by był bardziej wydajny, ale DZIAŁA! Dzięki za całą pomoc.

import sys, getopt, os, re 

sourceFolder = 'C:/MaxLogs' 
logFileName = sourceFolder + "/Test.log" 

print "--- START ----" 

lineStartsWith = re.compile('\[(0?[1-9]|[12][0-9]|3[01])(\/)(0?[1-9]|[12][0-9]|3[01])(\/)([0-9]{2})(\)') 
lineContains = re.compile('.*BMXAA6720W.*') 
lineEndsWith = re.compile('(?:.*milliseconds.*)') 

lines = [] 

multiLine = False 

with open(logFileName, 'r') as f: 
    for line in f: 
     if lineStartsWith.match(line) and lineContains.match(line) and lineEndsWith.match(line): 
      lines.append(line.replace("\n", " ")) 
     elif lineStartsWith.match(line) and lineContains.match(line) and not multiLine: 
      #Found the start of a multi-line entry 
      multiLineString = line 
      multiLine = True 
     elif multiLine and not lineEndsWith.match(line): 
      multiLineString = multiLineString + line 
     elif multiLine and lineEndsWith.match(line): 
      multiLineString = multiLineString + line 
      multiLineString = multiLineString.replace("\n", " ") 
      lines.append(multiLineString) 
      multiLine = False 

for line in lines: 
    print line 
+1

Czy próbowałeś użyć flagi 're.DOTALL'? Będziesz musiał sprawić, że część '. *' Będzie leniwą ('. *?'), A ponieważ czytasz wiersz po linii, zadziała, jeśli przeczytasz cały plik za jednym razem. Nie jestem jednak pewien co do implikacji dotyczących pamięci/wydajności. – Jerry

+0

Możesz spróbować odczytać cały plik, a następnie podzielić tekst za pomocą wyrażenia dopasowującego sygnatury czasowe bezpośrednio po znakach nowej linii. To powinno ci dać listę ciągłych pojedynczych logów, chyba że Twoi użytkownicy umieszczają rzeczy takie jak "\ n [8/21/13 11: 30: 33: 557 PDT]" "w swoich SQL ... w takim przypadku Ty Pewnie pojawiły się inne problemy. –

+0

Zaczynam się teraz zastanawiać, czy lepiej byłoby wrócić do linii po linii tak, żebym czytał wiersz po linii, jeśli linia pasuje do moich wartości "start" i "zawiera", mam mecz, który następnie musisz sprawdzić znacznik "końca" lub, jeśli nie jest, czytaj dalej i dołączaj linie, dopóki go nie znajdę. Myślę, że mam szczęście, ponieważ wiem, że początek i koniec zawsze będą tam, po prostu muszę ich szukać. – Chris

Odpowiedz

3

Czy muszę przetworzyć cały plik za jednym razem? Zwykle mają rozmiar 20 mb. Jak czytać cały plik i przeglądać go, szukając bloków jedno- lub wieloliniowych?

Dostępne są dwie opcje.

Można odczytać blok pliku po bloku, upewniając się, że na końcu każdego bloku zostanie dołączony dowolny "pozostawiony" bit do początku następnego i przeszukać każdy blok.Oczywiście będziesz musiał dowiedzieć się, co jest liczone jako "pozostawione", sprawdzając, jaki jest twój format danych i co może dopasować twoje wyrażenie, i teoretycznie możliwe jest, aby wiele bloków liczyło się jako resztki ...

Albo możesz tylko plik mmap. Mmap działa jak bajt (lub jak str w języku Python 2.x) i pozostawia go systemowi operacyjnemu do obsługi bloków stronicowania w razie potrzeby. Chyba że starasz się radzić sobie z absolutnie ogromnych plików (gigabajtów 32-bit, jeszcze w 64-bit), to jest trywialne i wydajny:

with open('bigfile', 'rb') as f: 
    with mmap.mmap(f.fileno(), length=0, access=mmap.ACCESS_READ) as m: 
     for match in compiled_re.finditer(m): 
      do_stuff(match) 

W starszych wersjach Pythona, mmap nie jest Menedżer kontekstu, więc musisz owinąć wokół niego contextlib.closing (lub po prostu użyć jawnego close, jeśli wolisz).


Jak napisać wielowierszowe wyrażenie, które pasowałoby do całej rzeczy w jednym wierszu lub było rozłożone na wiele linii?

Można użyć flagi DOTALL, która powoduje, że . pasuje do nowych linii. Zamiast tego możesz użyć flagi MULTILINE i wstawić odpowiednie znaki $ i/lub , ale to sprawia, że ​​proste przypadki są o wiele trudniejsze i rzadko jest to konieczne. Oto przykład z DOTALL (stosując prostszą regexp aby uczynić go bardziej oczywiste):

>>> s1 = """[8/21/13 11:30:33:557 PDT] 00000488 SystemOut  O 21 Aug 2013 11:30:33:557 [WARN] [MXServerUI01] [CID-UIASYNC-17464] BMXAA6720W - USER = (ABCDEF) SPID = (2526) app (ITEM) object (ITEM) : select * from item where ((status != 'OBSOLETE' and itemsetid = 'ITEMSET1') and (exists (select 1 from maximo.invvendor where (exists (select 1 from maximo.companies where ((contains(name,' $AAAA ') > 0)) and (company=invvendor.manufacturer and orgid=invvendor.orgid))) and (itemnum = item.itemnum and itemsetid = item.itemsetid)))) and (itemtype in (select value from synonymdomain where domainid='ITEMTYPE' and maxvalue = 'ITEM')) order by itemnum asc (execution took 2083 milliseconds)""" 
>>> s2 = """[8/21/13 11:30:33:557 PDT] 00000488 SystemOut  O 21 Aug 2013 11:30:33:557 [WARN] [MXServerUI01] [CID-UIASYNC-17464] BMXAA6720W - USER = (ABCDEF) SPID = (2526) app (ITEM) object (ITEM) : select * from item where ((status != 'OBSOLETE' and itemsetid = 'ITEMSET1') and 
    (exists (select 1 from maximo.invvendor where (exists (select 1 from maximo.companies where ((contains(name,' $AAAA ') > 0)) and (company=invvendor.manufacturer and orgid=invvendor.orgid))) and (itemnum = item.itemnum and itemsetid = item.itemsetid)))) and (itemtype in (select value from synonymdomain where domainid='ITEMTYPE' and maxvalue = 'ITEM')) order by itemnum asc (execution took 2083 milliseconds)""" 
>>> r = re.compile(r'\[(.*?)\].*?milliseconds\)', re.DOTALL) 
>>> r.findall(s1) 
['8/21/13 11:30:33:557 PDF'] 
>>> r.findall(s2) 
['8/21/13 11:30:33:557 PDF'] 

Jak widać drugi .*? dopasowane do nowej linii tak samo łatwo, jak przestrzeń.

Jeśli chcesz tylko traktować znak nowej linii jako spację, to również nie potrzebujesz; '\s' już łapie znaki nowej linii.

Na przykład:

>>> s1 = 'abc def\nghi\n' 
>>> s2 = 'abc\ndef\nghi\n' 
>>> r = re.compile(r'abc\s+def') 
>>> r.findall(s1) 
['abc def'] 
>>> r.findall(s2) 
['abc\ndef'] 
+0

Dobra wskazówka na temat używania mmap do przechowywania map dużych plików –

0

można odczytać cały plik na ciąg znaków, a następnie można użyć re.split aby zrobić listę wszystkich wpisów oddzielonych czasów. Oto przykład:

f = open(...) 
allLines = ''.join(f.readlines()) 
entries = re.split(regex, allLines) 
+0

Został specjalnie zapytany, jak uniknąć czytania całego pliku jako ciąg. Prawdopodobnie wie, że to możliwe i chce wiedzieć, jakie są alternatywy. – abarnert

+0

Nie widzę, że wyraźnie wspomniano nigdzie. Pliki mają rozmiar 20 MB, co jest żartem do czytania za jednym razem. – Chrismit

Powiązane problemy