如何用Go每10秒读取一个大文件的最后几行

wribegjk  于 2023-10-14  发布在  Go
关注(0)|答案(6)|浏览(156)

我如何从一个大的日志文件中读取最后两行而不将其完全加载到内存中?
我需要每10秒读一次(在赢的机器上).

package main

import (
    "fmt"
    "time"
    "os"
)

const MYFILE = "logfile.log"

func main() {
    c := time.Tick(10 * time.Second)
    for now := range c {
        readFile(MYFILE)
    }
}

func readFile(fname string){
    file, err:=os.Open(fname)
    if err!=nil{
        panic(err)
    }
    buf:=make([]byte, 32)
    c, err:=file.ReadAt(32, ????)
    fmt.Printf("%s\n", c)

}

日志文件类似于:

07/25/2013 11:55:42.400, 0.559
07/25/2013 11:55:52.200, 0.477
07/25/2013 11:56:02.000, 0.463
07/25/2013 11:56:11.800, 0.454
07/25/2013 11:56:21.600, 0.424
07/25/2013 11:56:31.400, 0.382
07/25/2013 11:56:41.200, 0.353
07/25/2013 11:56:51.000, 0.384
07/25/2013 11:57:00.800, 0.393
07/25/2013 11:57:10.600, 0.456

谢谢你,谢谢

gcuhipw9

gcuhipw91#

您可以使用file.seek()或file.ReadAt()直到几乎结束,然后阅读前进。你只能估计从哪里开始寻找,除非你知道2行= x字节。
您可以使用func(*File)Stat获取文件长度
下面是一个基于ReadAt、Stat和示例日志文件的示例:

package main

import (
    "fmt"
    "os"
    "time"
)

const MYFILE = "logfile.log"

func main() {
    c := time.Tick(10 * time.Second)
    for _ = range c {
        readFile(MYFILE)
    }
}

func readFile(fname string) {
    file, err := os.Open(fname)
    if err != nil {
        panic(err)
    }
    defer file.Close()

    buf := make([]byte, 62)
    stat, statErr := file.Stat()
    if statErr != nil {
        panic(statErr)
    }
    start := stat.Size() - 62
    _, err = file.ReadAt(buf, start)
    if err == nil {
        fmt.Printf("%s\n", buf)
    }

}
djmepvbi

djmepvbi2#

有些人会来到这个页面,寻找有效的阅读日志文件的最后一行(如tail命令行工具)。
这是我的版本,读取一个大文件的最后一行。它使用了前面的两个建议(使用Seek和File Stat)。
它逐字节反向读取文件(无需设置缓冲区大小),直到找到行的开头或文件的开头。

func getLastLineWithSeek(filepath string) string {
    fileHandle, err := os.Open(filepath)

    if err != nil {
        panic("Cannot open file")
        os.Exit(1)
    }
    defer fileHandle.Close()

    line := ""
    var cursor int64 = 0
    stat, _ := fileHandle.Stat()
    filesize := stat.Size()
    for { 
        cursor -= 1
        fileHandle.Seek(cursor, io.SeekEnd)

        char := make([]byte, 1)
        fileHandle.Read(char)

        if cursor != -1 && (char[0] == 10 || char[0] == 13) { // stop if we find a line
            break
        }

        line = fmt.Sprintf("%s%s", string(char), line) // there is more efficient way

        if cursor == -filesize { // stop if we are at the begining
            break
        }
    }

    return line
}
vlurs2pr

vlurs2pr3#

我认为File.Seek(0, 2)File.Read()的组合应该可以工作。
Seek调用会将您带到文件的末尾。你可以Seek到一个位置之前的一点,以获得最后几行。然后你Read,直到你的goroutine睡眠10秒;下一个Read有机会给你更多的数据。
您可以从GNU tail 's source中获得这个想法(以及最初显示最后几行的回扫描逻辑)。

qyuhtwio

qyuhtwio4#

好吧,这只是一个原始的想法,也许不是最好的方法,你应该检查和改进它,但似乎工作.
我希望有经验的Go用户也能做出贡献。
使用Stat,您可以获取文件的大小,并从中获取用于ReadAt的偏移量

func readLastLine(fname string) {
    file, err := os.Open(fname)
    if err != nil {
        panic(err)
    }
    defer file.Close()

    fi, err := file.Stat()
    if err != nil {
        fmt.Println(err)
    }

    buf := make([]byte, 32)
    n, err := file.ReadAt(buf, fi.Size()-int64(len(buf)))
    if err != nil {
        fmt.Println(err)
    }
    buf = buf[:n]
    fmt.Printf("%s", buf)

}
au9on6nz

au9on6nz5#

我使用tail来减少占用空间。不知道它是如何比较明智的表现。

// use "-1" as count for just last line
func printLastLines(count, path string) {
    c := exec.Command("tail", count, path)
    output, _ := c.Output()
    fmt.Println(string(output))
}

对于Windows,你必须这样做

func printLastWindows(count, path string) {
    ps, _ := exec.LookPath("powershell.exe")
    args := strings.Split(fmt.Sprintf(`Get-Content %s | Select-Object -last %s`, path, count), " ")
    c := exec.Command(ps, args...)
    output, _ := c.Output()
    fmt.Println(string(output))
}
kqqjbcuj

kqqjbcuj6#

下面是我写的以逆序方式阅读大字节的代码。它不会在尾随空格处中断。
这段代码所做的是反向循环字节,它计算遇到的字节数。当它检测到一个换行符时,它通过该数字循环回去,将该行和append()写入结果[]byte,然后重置数字。直到maxLine变量被满足。
这太复杂了,如果你只是想从特定的行中读取字节,可能有更好的方法。变量名一直渴望更容易阅读。

func ReverseByte(fileByte []byte, maxLine int) []byte {
    // This is a byte "code" for NewLine or "\n"
    nl := byte(10)

    var reverseFileByte []byte
    var lineLen, lineWritten int

    byteIndex := len(fileByte) - 1
    for lineWritten < maxLine {
        if fileByte[byteIndex] == nl {
            currentLine := make([]byte, lineLen)
            byteLineIndex := byteIndex
            var currentLineIndex int
            for currentLineIndex < lineLen {
                currentLine[currentLineIndex] = fileByte[byteLineIndex]
                byteLineIndex++
                currentLineIndex++
            }
            reverseFileByte = append(reverseFileByte, currentLine...)
            lineLen = 0
            lineWritten++
        }
        lineLen++
        byteIndex--
    }
    return reverseFileByte
}

https://go.dev/play/p/qKDFxiJQAfF

相关问题