Aktualizacja: zastąpiłem oryginalny kod zaktualizowaną wersją z licznymi ulepszeniami. Nazwijmy to (rzeczywistą?) Jakością alfa.
Ta wersja zawiera:
- obsługa opcji wiersza poleceń
- sprawdzanie poprawności formatu daty w wierszu poleceń
- niektóre
try
bloki
- czytanie linii przeniesiono do funkcji
Oryginalny tekst:
Co ty wiesz „Szukajcie”, a znajdziecie! Oto program w języku Python, który szuka w pliku i używa mniej więcej wyszukiwania binarnego. Jest znacznie szybszy niż skrypt AWK napisany przez innego faceta .
Jest to (wcześniej?) Jakość alfa. Powinien mieć try
bloki i sprawdzanie poprawności danych wejściowych oraz wiele testów i bez wątpienia powinien być bardziej Pythonic. Ale tutaj jest dla twojej rozrywki. Aha, i jest napisane dla Pythona 2.6.
Nowy kod:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# timegrep.py by Dennis Williamson 20100113
# in response to http://serverfault.com/questions/101744/fast-extraction-of-a-time-range-from-syslog-logfile
# thanks to serverfault user http://serverfault.com/users/1545/mike
# for the inspiration
# Perform a binary search through a log file to find a range of times
# and print the corresponding lines
# tested with Python 2.6
# TODO: Make sure that it works if the seek falls in the middle of
# the first or last line
# TODO: Make sure it's not blind to a line where the sync read falls
# exactly at the beginning of the line being searched for and
# then gets skipped by the second read
# TODO: accept arbitrary date
# done: add -l long and -s short options
# done: test time format
version = "0.01a"
import os, sys
from stat import *
from datetime import date, datetime
import re
from optparse import OptionParser
# Function to read lines from file and extract the date and time
def getdata():
"""Read a line from a file
Return a tuple containing:
the date/time in a format such as 'Jan 15 20:14:01'
the line itself
The last colon and seconds are optional and
not handled specially
"""
try:
line = handle.readline(bufsize)
except:
print("File I/O Error")
exit(1)
if line == '':
print("EOF reached")
exit(1)
if line[-1] == '\n':
line = line.rstrip('\n')
else:
if len(line) >= bufsize:
print("Line length exceeds buffer size")
else:
print("Missing newline")
exit(1)
words = line.split(' ')
if len(words) >= 3:
linedate = words[0] + " " + words[1] + " " + words[2]
else:
linedate = ''
return (linedate, line)
# End function getdata()
# Set up option handling
parser = OptionParser(version = "%prog " + version)
parser.usage = "\n\t%prog [options] start-time end-time filename\n\n\
\twhere times are in the form hh:mm[:ss]"
parser.description = "Search a log file for a range of times occurring yesterday \
and/or today using the current time to intelligently select the start and end. \
A date may be specified instead. Seconds are optional in time arguments."
parser.add_option("-d", "--date", action = "store", dest = "date",
default = "",
help = "NOT YET IMPLEMENTED. Use the supplied date instead of today.")
parser.add_option("-l", "--long", action = "store_true", dest = "longout",
default = False,
help = "Span the longest possible time range.")
parser.add_option("-s", "--short", action = "store_true", dest = "shortout",
default = False,
help = "Span the shortest possible time range.")
parser.add_option("-D", "--debug", action = "store", dest = "debug",
default = 0, type = "int",
help = "Output debugging information.\t\t\t\t\tNone (default) = %default, Some = 1, More = 2")
(options, args) = parser.parse_args()
if not 0 <= options.debug <= 2:
parser.error("debug level out of range")
else:
debug = options.debug # 1 = print some debug output, 2 = print a little more, 0 = none
if options.longout and options.shortout:
parser.error("options -l and -s are mutually exclusive")
if options.date:
parser.error("date option not yet implemented")
if len(args) != 3:
parser.error("invalid number of arguments")
start = args[0]
end = args[1]
file = args[2]
# test for times to be properly formatted, allow hh:mm or hh:mm:ss
p = re.compile(r'(^[2][0-3]|[0-1][0-9]):[0-5][0-9](:[0-5][0-9])?$')
if not p.match(start) or not p.match(end):
print("Invalid time specification")
exit(1)
# Determine Time Range
yesterday = date.fromordinal(date.today().toordinal()-1).strftime("%b %d")
today = datetime.now().strftime("%b %d")
now = datetime.now().strftime("%R")
if start > now or start > end or options.longout or options.shortout:
searchstart = yesterday
else:
searchstart = today
if (end > start > now and not options.longout) or options.shortout:
searchend = yesterday
else:
searchend = today
searchstart = searchstart + " " + start
searchend = searchend + " " + end
try:
handle = open(file,'r')
except:
print("File Open Error")
exit(1)
# Set some initial values
bufsize = 4096 # handle long lines, but put a limit them
rewind = 100 # arbitrary, the optimal value is highly dependent on the structure of the file
limit = 75 # arbitrary, allow for a VERY large file, but stop it if it runs away
count = 0
size = os.stat(file)[ST_SIZE]
beginrange = 0
midrange = size / 2
oldmidrange = midrange
endrange = size
linedate = ''
pos1 = pos2 = 0
if debug > 0: print("File: '{0}' Size: {1} Today: '{2}' Now: {3} Start: '{4}' End: '{5}'".format(file, size, today, now, searchstart, searchend))
# Seek using binary search
while pos1 != endrange and oldmidrange != 0 and linedate != searchstart:
handle.seek(midrange)
linedate, line = getdata() # sync to line ending
pos1 = handle.tell()
if midrange > 0: # if not BOF, discard first read
if debug > 1: print("...partial: (len: {0}) '{1}'".format((len(line)), line))
linedate, line = getdata()
pos2 = handle.tell()
count += 1
if debug > 0: print("#{0} Beg: {1} Mid: {2} End: {3} P1: {4} P2: {5} Timestamp: '{6}'".format(count, beginrange, midrange, endrange, pos1, pos2, linedate))
if searchstart > linedate:
beginrange = midrange
else:
endrange = midrange
oldmidrange = midrange
midrange = (beginrange + endrange) / 2
if count > limit:
print("ERROR: ITERATION LIMIT EXCEEDED")
exit(1)
if debug > 0: print("...stopping: '{0}'".format(line))
# Rewind a bit to make sure we didn't miss any
seek = oldmidrange
while linedate >= searchstart and seek > 0:
if seek < rewind:
seek = 0
else:
seek = seek - rewind
if debug > 0: print("...rewinding")
handle.seek(seek)
linedate, line = getdata() # sync to line ending
if debug > 1: print("...junk: '{0}'".format(line))
linedate, line = getdata()
if debug > 0: print("...comparing: '{0}'".format(linedate))
# Scan forward
while linedate < searchstart:
if debug > 0: print("...skipping: '{0}'".format(linedate))
linedate, line = getdata()
if debug > 0: print("...found: '{0}'".format(line))
if debug > 0: print("Beg: {0} Mid: {1} End: {2} P1: {3} P2: {4} Timestamp: '{5}'".format(beginrange, midrange, endrange, pos1, pos2, linedate))
# Now that the preliminaries are out of the way, we just loop,
# reading lines and printing them until they are
# beyond the end of the range we want
while linedate <= searchend:
print line
linedate, line = getdata()
if debug > 0: print("Start: '{0}' End: '{1}'".format(searchstart, searchend))
handle.close()
if debug > 0: print("File: '{0}' Size: {1} Today: '{2}' Now: {3} Start: '{4}' End: '{5}'".format(file, size, today, now, searchstar$
. Czysearchstar
ma to kończyć$
się literą, czy to literówka? W tej linii pojawia się błąd składniowy (wiersz 159)))
.$
powinno byćt, searchend))
tak, że mówi... searchstart, searchend))
Z szybkiego wyszukiwania w sieci istnieją rzeczy, które wyodrębniają na podstawie słów kluczowych (np. FIRE itp.), Ale nic, co wyodrębnia zakres dat z pliku.
Robienie tego, co proponujesz, nie wydaje się trudne:
Wydaje się to proste i mógłbym to dla ciebie napisać, jeśli nie masz nic przeciwko Ruby :)
źródło
Spowoduje to wydrukowanie zakresu wpisów między czasem rozpoczęcia a czasem zakończenia w oparciu o ich związek z bieżącym czasem („teraz”).
Stosowanie:
Przykład:
Opcja
-l
(długa) powoduje jak najdłuższe wyjście. Czas rozpoczęcia będzie interpretowany jako wczoraj, jeśli wartość godzin i minut czasu rozpoczęcia jest mniejsza niż zarówno godzina zakończenia, jak i teraz. Czas zakończenia będzie interpretowany jak dzisiaj, jeśli zarówno godzina rozpoczęcia, jak i godzina zakończenia GG: MM są większe niż „teraz”.Zakładając, że „teraz” to „11 stycznia 19:00”, w ten sposób będą interpretowane różne przykładowe czasy rozpoczęcia i zakończenia (bez
-l
wyjątku jak wspomniano):Prawie cały skrypt jest skonfigurowany. Ostatnie dwie linie wykonują całą pracę.
Ostrzeżenie: nie jest wykonywane sprawdzanie poprawności argumentów ani sprawdzanie błędów. Obudowy Edge nie zostały dokładnie przetestowane. Zostało to napisane przy użyciu
gawk
innych wersji AWK może skrzeczeć.Myślę, że AWK jest bardzo wydajny w wyszukiwaniu plików. Nie sądzę, aby cokolwiek innego mogło być szybsze w wyszukiwaniu nieindeksowanego pliku tekstowego.
źródło
Program C ++ stosujący wyszukiwanie binarne - do pracy z datami tekstowymi potrzebowałby kilku prostych modyfikacji (np. Wywołania strptime).
http://gitorious.org/bs_grep/
Miałem poprzednią wersję z obsługą dat tekstowych, jednak wciąż była zbyt wolna jak na skalę naszych plików dziennika; z profilowania wynika, że ponad 90% czasu spędzono na strptime, więc właśnie zmodyfikowaliśmy format dziennika, aby uwzględnić także numeryczny znacznik czasu unix.
źródło
Chociaż ta odpowiedź jest zbyt późna, dla niektórych może być korzystna.
Przekształciłem kod z @Dennis Williamson w klasę Python, której można używać do innych rzeczy w Pythonie.
Dodałem obsługę wielu dat.
źródło