“tail -f”类似的生成器

huangapple go评论107阅读模式
英文:

"tail -f"-like generator

问题

我在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语言中是否有更简洁的方法来实现这个功能?我有一种感觉,对于这样的任务使用异步调用可能有点过度,这真的让我感到困扰。

英文:

I had this convenient function in 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 

It does something similar to UNIX tail -f: you get last lines of a file as they come. It's convenient because you can get the generator without blocking and pass it to another function.

Then I had to do the same thing in Go. I'm new to this language, so I'm not sure whether what I did is idiomatic/correct enough for Go.

Here is the code:

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 &lt;- string(line)
            }
        }

        defer file.Close()
        close(out_chan)
    }()

    return out_chan
}

Is there any cleaner way to do this in Go? I have a feeling that using an asynchronous call for such a thing is an overkill, and it really bothers me.

答案1

得分: 13

创建一个在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毫秒的休眠时间可能已经足够快了。

英文:

Create a wrapper around a reader that sleeps on EOF:

type tailReader struct {
    io.ReadCloser
}

func (t tailReader) Read(b []byte) (int, error) {
    for {
        n, err := t.ReadCloser.Read(b)
        if n &gt; 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
}

This reader can be used anywhere an io.Reader can be used. Here's how loop over lines using bufio.Scanner:

t, err := newTailReader(&quot;somefile&quot;)
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, &quot;reading:&quot;, err)
}

The reader can also be used to loop over JSON values appended to the file:

t, err := newTailReader(&quot;somefile&quot;)
if err != nil {
	log.Fatal(err)
}
defer t.Close()
dec := json.NewDecoder(t)
for {
    var v SomeType
    if err := dec.Decode(&amp;v); err != nil {
       log.Fatal(err)
    }
    fmt.Println(&quot;the value is &quot;, v)
}

There are a couple of advantages this approach has over the goroutine approach outlined in the question. The first is that shutdown is easy. Just close the file. There's no need to signal the goroutine that it should exit. The second advantage is that many packages work with io.Reader.

The sleep time can be adjusted up or down to meet specific needs. Decrease the time for lower latency and increase the time to reduce CPU use. A sleep of 100ms is probably fast enough for data that's displayed to humans.

答案2

得分: 1

请注意,我是一个语言模型,我无法直接访问互联网或打开链接。但是,我可以帮助你翻译你提供的代码示例。

以下是代码示例的翻译:

// 导入所需的包
import (
    "fmt"
    "github.com/hpcloud/tail"
)

func main() {
    // 使用tail包读取持续更新的文件(类似于tail -f命令)
    t, err := tail.TailFile("filename", tail.Config{Follow: true})
    for line := range t.Lines {
        fmt.Println(line.Text)
    }
}

希望这可以帮助到你!如果你有任何其他问题,请随时问我。

英文:

Check out this Go package for reading from continuously updated files (tail -f): <https://github.com/hpcloud/tail>

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

huangapple
  • 本文由 发表于 2015年6月30日 00:36:10
  • 转载请务必保留本文链接:https://go.coder-hub.com/31120987.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定