.e.g. 1. logfile
- Start
- Line1
- Line2
- Line3
- End
I am able to get the seek position of Line1 when I read the file from beginning.
func getSeekLocation() int64 {
start := int64(0)
input, err := os.Open(logFile)
if err != nil {
fmt.Println(err)
}
if _, err := input.Seek(start, io.SeekStart); err != nil {
fmt.Println(err)
}
scanner := bufio.NewScanner(input)
pos := start
scanLines := func(data []byte, atEOF bool) (advance int, token []byte,
err error) {
advance, token, err = bufio.ScanLines(data, atEOF)
pos += int64(advance)
return
}
scanner.Split(scanLines)
for scanner.Scan() {
if strings.Contains(scanner.Text(), "Line1") {
break
}
}
size, err := getFileSize()
if err != nil {
fmt.Println(err)
}
return size - pos
}
But this is not an efficient way to solve the problem because as the file size increases the time to get the location will also increase. I would like to get the location of the line from the EOF location which I think would be more efficient.
Note: I optimized and improved the below solution, and released it as a library here:
github.com/icza/backscanner
bufio.Scanner
uses anio.Reader
as its source, which does not support seeking and / or reading from arbitrary positions, so it is not capable of scanning lines from the end.bufio.Scanner
can only read any part of the input once all data preceeding it has already been read (that is, it can only read the end of the file if it reads all the file's content first).So we need a custom solution to implement such functionality. Fortunately
os.File
does support reading from arbitrary positions as it implements bothio.Seeker
andio.ReaderAt
(any of them would be sufficient to do what we need).Scanner that returns lines going backward, starting at the end
Let's construct a
Scanner
which scans lines backward, starting with the last line. For this, we'll utilize anio.ReaderAt
. The following implementation uses an internal buffer into which data is read by chunks, starting from the end of the input. The size of the input must also be passed (which is basically the position where we want to start reading from, which must not necessarily be the end position).Example using it:
Output (try it on the Go Playground):
Notes:
Scanner
does not limit max length of lines, it handles all.Scanner
handles both\n
and\r\n
line endings (ensured by thedropCR()
function).Scanner
does not reuse buffers, always creates new ones when needed. It would be enough to (pre)allocate 2 buffers, and use those wisely. Implementation would become more complex, and it would introduce a max line length limit.Using it with a file
To use this
Scanner
with a file, you may useos.Open()
to open a file. Note that*File
implementsio.ReaderAt()
. Then you may useFile.Stat()
to obtain info about the file (os.FileInfo
), including its size (length):Looking for a substring in a line
If you're looking for a substring in a line, then simply use the above
Scanner
which returns the starting pos of each line, reading lines from the end.You may check the substring in each line using
strings.Index()
, which returns the substring position inside the line, and if found, add the line start position to this.Let's say we're looking for the
"ine2"
substring (which is part of the"Line2"
line). Here's how you can do that:Output (try it on the Go Playground):