G
Behaviour may depend on the platform, but it is only possible to try periodically to read the file after EOF (or at worst to memorize the last position and to cause it) file.seek(last_position) On the re-operated file, suggesting that new lines are added in the file only at the end (as in the log file) and no other changes. For example, to show GUI the last line in a file that corresponds to a given regular expression (a tax) tail -f file | grep -Pe regex#!/usr/bin/env python3
"""Usage: grep-tail <regex> <file>"""
import collections
import functools
import re
import sys
import tkinter.messagebox
def filter_lastline(file, predicate):
"""Find the last line in file that satisfies predicate."""
lines = collections.deque(filter(predicate, file), maxlen=1)
try:
return lines.pop().rstrip('\n')
except IndexError:
return '' # not found
def update_label(root, label, last_line):
current = label['text']
new = last_line()
if new and current != new:
label['text'] = new # update label
root.after(1000, update_label, root, label, last_line) # poll in a second
def main():
root = tkinter.Tk()
root.withdraw() # hide the main window
try: # handle command-line arguments
regex_string, path = sys.argv[1:]
found = re.compile(regex_string).search
file = open(path)
except Exception as e:
tkinter.messagebox.showerror('wrong command-line arguments',
'error: %s\n%s' % (e, doc),
parent=root)
sys.exit(doc)
last_line = functools.partial(filter_lastline, file, found)
label = tkinter.Label(root, text=last_line()
or '<nothing matched %r>' % regex_string)
label.pack()
update_label(root, label, last_line) # start polling
# center window
root.eval('tk::PlaceWindow %s center' %
root.winfo_pathname(root.winfo_id()))
root.mainloop()
main()
Example:$ ./grep-tail 'python[23]' /var/log/syslog
Comments on implementationThe file is an iterator over the lines in Pitton, so filter(predicate, file) generates lines in the file that satisfy predicate(line) criterion (regular expression in this case).deque(it, maxlen=1) absorbs the terator, leaving only the last element.Repeat call filter_lastline(file, predicate)♪ file is read from the last position (EOF - from the previous end of the file). Can the new rows be read in such a way as not rediscovered, depending on the platformroot.after(1000, f, *args) Call. f(*args) In a second, so:def f(*args):
# do something
# continue loop
root.after(1000, f, *args)
creates a cycle without blocking GUI. You can't write:def loop():
while True:
f(*args)
time.sleep(1)
♪ loop() blocks GUI and will have to be called into a separate flow/process. root.after() Allowance f(*args) GUI stream call and modify label No problem.If the file is rarely changed, efficiency can be achieved http://pythonhosted.org/watchdog/quickstart.html use to cause update_label() Only when the file really changed. http://pythonhosted.org/watchdog/api.html#watchdog.events.FileSystemEventHandler.on_modified ) In this case (updates 5-15 seconds), use watchdog It would be overcomplicated (extraneous dependence + integration with the event cycle):#!/usr/bin/env python3
"""Usage: grep-tail <regex> <file>"""
import collections
import functools
import os
import re
import sys
import tkinter.messagebox
from watchdog.observers import Observer # $ pip install watchdog
from watchdog.events import FileSystemEventHandler
def filter_lastline(file, predicate):
"""Find the last line in file that satisfies predicate."""
lines = collections.deque(filter(predicate, file), maxlen=1)
try:
return lines.pop().rstrip('\n')
except IndexError:
return '' # not found
def update_label(root, label, last_line):
current = label['text']
new = last_line()
if new and current != new:
label['text'] = new # update label
def main():
root = tkinter.Tk()
root.withdraw() # hide the main window
try: # handle command-line arguments
regex_string, path = sys.argv[1:]
found = re.compile(regex_string).search
file = open(path)
except Exception as e:
tkinter.messagebox.showerror('wrong command-line arguments',
'error: %s\n%s' % (e, doc),
parent=root)
sys.exit(doc)
last_line = functools.partial(filter_lastline, file, found)
label = tkinter.Label(root, text=last_line()
or '<nothing matched %r>' % regex_string)
label.pack()
class EventHandler(FileSystemEventHandler):
def on_modified(self, event):
if event.src_path == path:
update_label(root, label, last_line)
observer = Observer()
observer.schedule(EventHandler(), os.path.dirname(path))
observer.start()
# center window
root.eval('tk::PlaceWindow %s center' %
root.winfo_pathname(root.winfo_id()))
root.mainloop()
observer.stop()
observer.join()
main()
In contrast to the previous version, update_label() Only if the entry file was changed: no root.after() Call. Full lines are supposed to be written in plain for log files and smaller buffalo lines, otherwise should be update_label() editing to accumulate data on every challenge until a new line meets.