我在Python中有这个方便的功能:
def follow(path): with open(self.path) as lines: lines.seek(0, 2) # seek to EOF while True: line = lines.readline() if not line: time.sleep(0.1) continue yield line
它的作用类似于UNIX tail -f:文件的最后几行出现。这是方便,因为你可以得到发电机 无阻塞 ,并将它传递给另一个函数。
tail -f
然后我必须在Go中执行相同的操作。我是该语言的新手,所以不确定我所做的操作是否足够习惯/正确。
这是代码:
func Follow(fileName string) chan string { out_chan := make(chan string) file, err := os.Open(fileName) if err != nil { log.Fatal(err) } file.Seek(0, os.SEEK_END) bf := bufio.NewReader(file) go func() { for { line, _, _ := bf.ReadLine() if len(line) == 0 { time.Sleep(10 * time.Millisecond) } else { out_chan <- string(line) } } defer file.Close() close(out_chan) }() return out_chan }
Go中有没有更干净的方法可以做到这一点?我觉得对这样的事情使用异步调用实在是太过分了,这确实让我感到困扰。
我建议围绕在EOF上运行的阅读器创建包装器:
type tailReader struct { io.ReadCloser } func (t tailReader) Read(b []byte) (int, error) { for { n, err := t.ReadCloser.Read(b) if n > 0 { return n, nil } else if err != io.EOF { return n, err } time.Sleep(10 * time.Millisecond) } } func newTailReader(fileName string) (tailReader, error) { f, err := os.Open(fileName) if err != nil { return tailReader{}, err } if _, err := f.Seek(0, 2); err != nil { return tailReader{}, err } return tailReader{f}, nil }
该阅读器可以在任何可以使用io.Reader的地方使用。这是使用bufio.Scanner循环行的方法:
t, err := newTailReader("somefile") if err != nil { log.Fatal(err) } defer t.Close() scanner := bufio.NewScanner(t) for scanner.Scan() { fmt.Println(scanner.Text()) } if err := scanner.Err(); err != nil { fmt.Fprintln(os.Stderr, "reading:", err) }
阅读器还可用于循环附加到文件的JSON值:
t, err := newTailReader("somefile") if err != nil { log.Fatal(err) } defer t.Close() dec := json.NewDecoder(t) for { var v SomeType if err := dec.Decode(&v); err != nil { log.Fatal(err) } fmt.Println("the value is ", v) }
与问题中概述的goroutine方法相比,这种方法有两个优点。首先是关机很容易。只需关闭文件。无需向goroutine发出退出信号的信号。第二个优点是许多软件包都可以与io.Reader一起使用。
可以调整睡眠时间,以满足特定需求。减少时间以降低延迟,并增加时间以减少CPU使用量。100ms的睡眠对于显示给人类的数据可能足够快。