在 Go 中實現 tail 的跟蹤功能
【導讀】本文介紹了一種 go 實現 tail 功能的思路與實現,此實現在實際項目中可以用於監聽文件變更。
tail
是我們大多數人都熟悉的命令。我假設你也熟悉提供的 -f 選項。如果你不熟悉,知道它會打印出文件的最後幾行即可。最近在一個項目上工作,我想知道我需要做什麼來實現這個功能。這個想法來自閱讀 Feynman 的書:
毫無疑問,你知道如何去做; 但是當你像小孩子一樣玩這類問題,並且你沒有看到答案時... 試圖找出如何去做是很有趣的。然後,當你進入成年時,你會培養出一定的自信,你可以去發現事物; 但是如果他們已經被發現,那你根本不應該再來打擾自己。一個傻瓜能做的事,另一個傻瓜也能做,其他一些傻瓜打你的事實不應該打擾你:你應該爲將要發現的事物而快樂。
實現它可能是一件小事。但我認爲這將是一系列文章中的一個良好的開端,在這篇文章中我寫了如何實現一些東西。因此,讓我帶你看看如何在 Go 中實現 tail -f
的過程。
首先,讓我們瞭解這個問題。tail 命令提供了一個標誌,可以 “跟蹤” 文件。它所做的是等待添加到文件末尾的任何更改並將其打印出來。爲了簡單起見,我不打算實現 tail,而只是實現跟蹤功能。所以我們在開始時打印整個文件,然後在添加它們時打印行。
我首先想到的是這樣做的非常幼稚的方式。打印文件中的所有字節,直到達到 io.EOF
; 讓這個過程睡一會兒,然後再試一次。我們來看看這個函數:
func follow(file io.Reader) error {
r := bufio.NewReader(file)
for {
by, err := r.ReadBytes('\n')
if err != nil && err != io.EOF {
return err
}
fmt.Print(string(by))
if err == io.EOF {
time.Sleep(time.Second)
}
}
}
當內容寫入文件時,可悲的是沒有及時做出反應。Linux 提供了一個 API 來監視文件系統事件:inotify AP
。手冊頁給了你一個很好的介紹。它提供了兩個我們感興趣的函數:inotify_init
和 inotify_add_watch
。inotify_init
函數創建一個對象,我們將使用該對象進一步與 API 進行交互。inotify_add_watch
函數允許你指定感興趣的文件事件。API 提供了幾個事件,但我們關心的是修改文件時發出的 IN_MODIFY
事件。
由於我們使用 Go,不得不列出 syscall 包。它爲前面提到的功能提供了包裝器:syscall.InotifyInit
和 syscall.InotifyAddWatch
。使用 syscall
讓我們看看如何實現 follow
函數。爲了簡潔起見,我省略了錯誤處理,當你看到一個 _ 變量被使用時,它是處理返回錯誤的好地方。
func follow(filename string) error {
file, _ := os.Open(filename)
fd, _ := syscall.InotifyInit()
_, _ := syscall.InotifyAddWatch(fd, filename, syscall.IN_MODIFY)
r := bufio.NewReader(file)
for {
by, err := r.ReadBytes('\n')
if err != nil && err != io.EOF {
return err
}
fmt.Print(string(by))
if err != io.EOF {
continue
}
if err = waitForChange(fd); err != nil {
return err
}
}
}
func waitForChange(fd int) error {
for {
var buf [syscall.SizeofInotifyEvent]byte
_, _ := syscall.Read(fd, buf[:])
if err != nil {
return err
}
r := bytes.NewReader(buf[:])
var ev = syscall.InotifyEvent{}
_ = binary.Read(r, binary.LittleEndian, &ev)
if ev.Mask&syscall.IN_MODIFY == syscall.IN_MODIFY {
return nil
}
}
}
InotifyInit
函數返回一個可用於讀取 sycall.InotifyEvent
的文件處理程序。從這個處理程序讀取是一個阻塞操作。這使我們只有在創建事件時才做出反應。
如果您要處理多個操作系統,最好更一般地處理這個操作系統。這就是 fsnotify 軟件包的來源。它提供了一個針對 Linux 的 inotify
,BSD 的 kqueue
等的抽象。使用 fsnotify
我們的函數看起來與前面的非常相似,但是更簡單。
func follow(filename string) error {
file, _ := os.Open(filename)
watcher, _ := fsnotify.NewWatcher()
defer watcher.Close()
_ = watcher.Add(filename)
r := bufio.NewReader(file)
for {
by, err := r.ReadBytes('\n')
if err != nil && err != io.EOF {
return err
}
fmt.Print(string(by))
if err != io.EOF {
continue
}
if err = waitForChange(watcher); err != nil {
return err
}
}
}
func waitForChange(w *fsnotify.Watcher) error {
for {
select {
case event := <-w.Events:
if event.Op&fsnotify.Write == fsnotify.Write {
return nil
}
case err := <-w.Errors:
return err
}
}
}
我希望代碼解釋得比我寫的文本更好。爲了簡潔,我省略了代碼可能失敗的幾種情況。爲了完善功能,我必須深入挖掘。
但這足以讓我對其產生了知識的渴望:它到底是如何工作的。希望你也由此感覺。
轉自:Go 語言中文網
studygolang.com/articles/12489
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/Pbm2KatlvKB77j9WWaqoWw