【问题标题】:"tail -f"-like generator类似“tail -f”的生成器
【发布时间】:2015-09-16 05:30:42
【问题描述】:

我在 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 的事情:你会得到文件的最后几行。这很方便,因为您可以在不阻塞的情况下获取生成器并将其传递给另一个函数。

然后我不得不在 Go 中做同样的事情。我是这门语言的新手,所以我不确定我所做的对于 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 中有没有更简洁的方法来做到这一点?我有一种感觉,对这样的事情使用异步调用有点过头了,这真的让我很困扰。

【问题讨论】:

  • 使用os.SEEK_END 而不是2time.Sleep(10) 睡眠 10 纳秒,你可能是指 time.Sleep(10 * time.Millisecond)。一旦io.Reader 给你io.EOF,你就不应该期望它会给你更多数据; EOF == 结束文件/steam,没有更多数据了。
  • 谢谢,我编辑了代码。

标签: file go generator


【解决方案1】:

我建议为在 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 使用。对于显示给人类的数据来说,100 毫秒的睡眠可能已经足够快了。

【讨论】:

  • 我看到有两件事是错误的...... [1] time.Sleep(10*time.Millisecond) 将导致系统产生热量,而不是安静地坐着直到实际上有一些数据。 [2] 当您在文件末尾时,主循环没有信号,因此您可以决定退出。 (我有一个用例,我向服务发送一些命令;然后监视等待肯定答复的日志;由于没有否定答复,我需要耗尽数据或检查计时器。Goroutines 和关闭数据源是不是一个选项。
  • @Richard (1) 向上或向下调整睡眠时间以平衡延迟与功率。 (2) 如果您需要在某个截止日期之前耗尽数据,则修改答案中的代码以在休眠前检查截止日期。
【解决方案2】:

查看这个 Go 包以读取不断更新的文件 (tail -f):https://github.com/hpcloud/tail

t, err := tail.TailFile("filename", tail.Config{Follow: true})
for line := range t.Lines {
    fmt.Println(line.Text)
}

【讨论】:

    猜你喜欢
    • 2011-02-19
    • 2010-11-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-08-03
    • 2017-05-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多