本篇內(nèi)容主要講解“怎么用Python完成tail命令”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“怎么用Python完成tail命令”吧!
創(chuàng)新互聯(lián)專業(yè)為企業(yè)提供湘潭網(wǎng)站建設、湘潭做網(wǎng)站、湘潭網(wǎng)站設計、湘潭網(wǎng)站制作等企業(yè)網(wǎng)站建設、網(wǎng)頁設計與制作、湘潭企業(yè)網(wǎng)站模板建站服務,十載湘潭做網(wǎng)站經(jīng)驗,不只是建網(wǎng)站,更提供有價值的思路和整體網(wǎng)絡服務。
主要思路是: 打開文件, 把指針移動到文件最后, 然后有數(shù)據(jù)則輸出數(shù)據(jù), 無數(shù)據(jù)則休眠一段時間.
import time import sys from typing import Callable, NoReturn class Tail(object): def __init__( self, file_name: str, output: Callable[[str], NoReturn] = sys.stdout.write, interval: int = 1 ): self.file_name: str = file_name self.output: Callable[[str], NoReturn] = output self.interval: int = interval def __call__(self): with open(self.file_name) as f: f.seek(0, 2) # 從文件結尾處開始seek while True: line: str = f.readline() if line: self.output(line) # 使用print都會每次都打印新的一行 else: time.sleep(self.interval) if __name__ == '__main__': filename: str = sys.argv[0] Tail(filename)()
之后只要做如下調(diào)用即可:
python xxx.py filename
tail -f
默認先讀取最后10行數(shù)據(jù),再從文件尾部讀取實時數(shù)據(jù).如果對于小文件,可以先讀取所有文件內(nèi)容,并輸出最后10行, 但是讀取全文再獲取最后10行的性能不高, 而從后滾10行的邊界條件也很復雜, 先看先讀取全文再獲取最后10行的實現(xiàn):
import time import sys from typing import Callable, NoReturn class Tail(object): def __init__( self, file_name: str, output: Callable[[str], NoReturn] = sys.stdout.write, interval: int = 1 ): self.file_name: str = file_name self.output: Callable[[str], NoReturn] = output self.interval: int = interval def __call__(self): with open(self.file_name) as f: self.read_last_line(f) while True: line: str = f.readline() if line: self.output(line) # 使用print都會每次都打印新的一行 else: time.sleep(self.interval) def read_last_line(self, f): last_lines = f.readlines()[-10:] for line in last_lines: self.output(line) if __name__ == '__main__': filename: str = sys.argv[0] Tail(filename)()
可以看到實現(xiàn)很簡單, 相比第一版只多了個read_last_line的函數(shù)
, 接下來就要解決性能的問題了, 當文件很大的時候, 這個邏輯是不行的, 特別是有些日志文件經(jīng)常有幾個G大, 如果全讀出來內(nèi)存就爆了. 而在Linux系統(tǒng)中, 沒有一個接口可以指定指針跳到倒數(shù)10行, 只能使用如下方法來模擬輸出倒數(shù)10行:
首先游標跳轉到最新的字符, 保存當前游標, 然后預估一行數(shù)據(jù)的字符長度, 最好偏多, 這里我按1024字符長度為一行來處理
然后利用seek的方法,跳轉到seek(-1024 * 10, 2)的字符, 這就是我們預估的倒數(shù)10行內(nèi)的內(nèi)容
接著對內(nèi)容進行判斷, 如果跳轉的字符長度小于 10 * 1024, 則證明整個文件沒有10行, 則采用原來的read_last_line
方法.
如果跳轉到字符長度等于1024 * 10, 則利用換行符計算已取字符長度共有多少行,如果行數(shù)大于10,那只輸出最后10行,如果只讀了4行,則繼續(xù)讀6*1024,直到讀滿10行為止
通過以上步奏, 就把倒數(shù)10行的數(shù)據(jù)計算好了可以打印出來, 可以進入追加數(shù)據(jù)了, 但是這時候文件內(nèi)容可能發(fā)生改變了, 我們的游標也發(fā)生改變了, 這時候要把游標跳回到剛才保存的游標,防止漏打或者重復打印數(shù)據(jù).
分析完畢后, 就可以開始重構read_last_line
函數(shù)了.
import time import sys from typing import Callable, List, NoReturn class Tail(object): def __init__( self, file_name: str, output: Callable[[str], NoReturn] = sys.stdout.write, interval: int = 1, len_line: int = 1024 ): self.file_name: str = file_name self.output: Callable[[str], NoReturn] = output self.interval: int = interval self.len_line: int = len_line def __call__(self, n: int = 10): with open(self.file_name) as f: self.read_last_line(f, n) while True: line: str = f.readline() if line: self.output(line) # 使用print都會每次都打印新的一行 else: time.sleep(self.interval) def read_last_line(self, file, n): read_len: int = self.len_line * n # 跳轉游標到最后 file.seek(0, 2) # 獲取當前結尾的游標位置 now_tell: int = file.tell() while True: if read_len > file.tell(): # 如果跳轉的字符長度大于原來文件長度,那就把所有文件內(nèi)容打印出來 file.seek(0) # 由于read方法是按照游標進行打印, 所以要重置游標 last_line_list: List[str] = file.read().split('\n')[-n:] # 重新獲取游標位置 now_tell: int = file.tell() break # 跳轉到我們預估的字符位置 file.seek(-read_len, 2) read_str: str = file.read(read_len) cnt: int = read_str.count('\n') if cnt >= n: # 如果獲取的行數(shù)大于要求的行數(shù),則獲取前n行的行數(shù) last_line_list: List[str] = read_str.split('\n')[-n:] break else: # 如果獲取的行數(shù)小于要求的行數(shù),則預估需要獲取的行數(shù),繼續(xù)獲取 if cnt == 0: line_per: int = read_len else: line_per: int = int(read_len / cnt) read_len = line_per * n for line in last_line_list: self.output(line + '\n') # 重置游標,確保接下來打印的數(shù)據(jù)不重復 file.seek(now_tell) if __name__ == '__main__': import argparse parser = argparse.ArgumentParser() parser.add_argument("-f", "--filename") parser.add_argument("-n", "--num", default=10) args, unknown = parser.parse_known_args() if not args.filename: raise RuntimeError('filename args error') Tail(args.filename)(int(args.num))
可以發(fā)現(xiàn)實時讀取那塊的邏輯性能還是很差, 如果每秒讀一次文件,實時性就太慢了,把間隔改小了,則處理器占用太多. 性能最好的情況是如果能得知文件更新再進行打印文件, 那性能就能得到保障了.慶幸的是,在Linux中inotify
提供了這樣的功能. 此外,日志文件有一個特點就是會進行l(wèi)ogrotate,如果日志被logrotate了,那我們就需要重新打開文件,并進一步讀取數(shù)據(jù), 這種情況也可以利用到inotify
, 當inotify
獲取到文件重新打開的事件時,我們就重新打開文件,再讀取.
import os import sys from typing import Callable, List, NoReturn import pyinotify multi_event = pyinotify.IN_MODIFY | pyinotify.IN_MOVE_SELF # 監(jiān)控多個事件 class InotifyEventHandler(pyinotify.ProcessEvent): # 定制化事件處理類,注意繼承 """ 執(zhí)行inotify event的封裝 """ f: 'open()' filename: str path: str wm: 'pyinotify.WatchManager' output: Callable def my_init(self, **kargs): """pyinotify.ProcessEvent要求不能直接繼承__init__, 而是要重寫my_init, 我們重寫這一段并進行初始化""" # 獲取文件 filename: str = kargs.pop('filename') if not os.path.exists(filename): raise RuntimeError('Not Found filename') if '/' not in filename: filename = os.getcwd() + '/' + filename index = filename.rfind('/') if index == len(filename) - 1 or index == -1: raise RuntimeError('Not a legal path') self.f = None self.filename = filename self.output: Callable = kargs.pop('output') self.wm = kargs.pop('wm') # 只監(jiān)控路徑,這樣就能知道文件是否移動 self.path = filename[:index] self.wm.add_watch(self.path, multi_event) def read_line(self): """統(tǒng)一的輸出方法""" for line in self.f.readlines(): self.output(line) def process_IN_MODIFY(self, event): """必須為process_事件名稱,event表示事件對象, 這里表示監(jiān)控到文件發(fā)生變化, 進行文件讀取""" if event.pathname == self.filename: self.read_line() def process_IN_MOVE_SELF(self, event): """必須為process_事件名稱,event表示事件對象, 這里表示監(jiān)控到文件發(fā)生重新打開, 進行文件讀取""" if event.pathname == self.filename: # 檢測到文件被移動重新打開文件 self.f.close() self.f = open(self.filename) self.read_line() def __enter__(self) -> 'InotifyEventHandler': self.f = open(self.filename) return self def __exit__(self, exc_type, exc_val, exc_tb): self.f.close() class Tail(object): def __init__( self, file_name: str, output: Callable[[str], NoReturn] = sys.stdout.write, interval: int = 1, len_line: int = 1024 ): self.file_name: str = file_name self.output: Callable[[str], NoReturn] = output self.interval: int = interval self.len_line: int = len_line wm = pyinotify.WatchManager() # 創(chuàng)建WatchManager對象 inotify_event_handler = InotifyEventHandler( **dict(filename=file_name, wm=wm, output=output) ) # 實例化我們定制化后的事件處理類, 采用**dict傳參數(shù) wm.add_watch('/tmp', multi_event) # 添加監(jiān)控的目錄,及事件 self.notifier = pyinotify.Notifier(wm, inotify_event_handler) # 在notifier實例化時傳入,notifier會自動執(zhí)行 self.inotify_event_handle: 'InotifyEventHandler' = inotify_event_handler def __call__(self, n: int = 10): """通過inotify的with管理打開文件""" with self.inotify_event_handle as i: # 先讀取指定的行數(shù) self.read_last_line(i.f, n) # 啟用inotify的監(jiān)聽 self.notifier.loop() def read_last_line(self, file, n): read_len: int = self.len_line * n # 獲取當前結尾的游標位置 file.seek(0, 2) now_tell: int = file.tell() while True: if read_len > file.tell(): # 如果跳轉的字符長度大于原來文件長度,那就把所有文件內(nèi)容打印出來 file.seek(0) last_line_list: List[str] = file.read().split('\n')[-n:] # 重新獲取游標位置 now_tell: int = file.tell() break file.seek(-read_len, 2) read_str: str = file.read(read_len) cnt: int = read_str.count('\n') if cnt >= n: # 如果獲取的行數(shù)大于要求的行數(shù),則獲取前n行的行數(shù) last_line_list: List[str] = read_str.split('\n')[-n:] break else: # 如果獲取的行數(shù)小于要求的行數(shù),則預估需要獲取的行數(shù),繼續(xù)獲取 if cnt == 0: line_per: int = read_len else: line_per: int = int(read_len / cnt) read_len = line_per * n for line in last_line_list: self.output(line + '\n') # 重置游標,確保接下來打印的數(shù)據(jù)不重復 file.seek(now_tell) if __name__ == '__main__': import argparse parser = argparse.ArgumentParser() parser.add_argument("-f", "--filename") parser.add_argument("-n", "--num", default=10) args, unknown = parser.parse_known_args() if not args.filename: raise RuntimeError('filename args error') Tail(args.filename)(int(args.num))
可以看到, 從原本的open打開文件改為用inotify打開文件(這時候會調(diào)用my_init方法進行初始化), 打開后還是運行我們打開原來n行的代碼, 然后就交給inotify運行. 在inotify運行之前, 我們把重新打開文件方法和打印文件方法都掛載在inotifiy對應的事件里, 之后inotify運行時, 會根據(jù)對應的事件執(zhí)行對應的方法。
到此,相信大家對“怎么用Python完成tail命令”有了更深的了解,不妨來實際操作一番吧!這里是創(chuàng)新互聯(lián)網(wǎng)站,更多相關內(nèi)容可以進入相關頻道進行查詢,關注我們,繼續(xù)學習!
當前標題:怎么用Python完成tail命令
URL網(wǎng)址:http://jinyejixie.com/article32/pdchsc.html
成都網(wǎng)站建設公司_創(chuàng)新互聯(lián),為您提供Google、、品牌網(wǎng)站設計、虛擬主機、營銷型網(wǎng)站建設、網(wǎng)站策劃
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉載內(nèi)容為主,如果涉及侵權請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉載,或轉載時需注明來源: 創(chuàng)新互聯(lián)