Skip to content

Instantly share code, notes, and snippets.

@scorredoira
Last active October 16, 2016 16:26
Show Gist options
  • Save scorredoira/cb1a564978fca9805f9c3c6c1a86aed0 to your computer and use it in GitHub Desktop.
Save scorredoira/cb1a564978fca9805f9c3c6c1a86aed0 to your computer and use it in GitHub Desktop.
read a file from the end
// Tail returns the last n lines of a file. It's not optimized but scans
// from the end so it's fast for large files.
func Tail(path string, lines int) ([]string, error) {
f, err := os.Open(path)
if err != nil {
return nil, err
}
defer f.Close()
s, err := os.Stat(path)
if err != nil {
return nil, err
}
size := s.Size()
start, err := tailStart(f, size, lines)
if err != nil {
return nil, err
}
buf := make([]byte, size-start)
_, err = f.ReadAt(buf, start)
if err != nil {
return nil, err
}
// remove Windows \r
txt := strings.Replace(string(buf), "\r", "", -1)
ls := strings.Split(txt, "\n")
// if we got more lines than requested take the last ones.
l := len(ls)
if l > lines {
ls = ls[l-lines:]
}
return ls, nil
}
// return where to start reading to read n lines from the end.
func tailStart(f *os.File, size int64, lines int) (int64, error) {
// assume 1KB per line. It's an arbitrary value but most lines would be < 200B.
const blockSize = 1000
block := int64(blockSize * lines)
if block > size {
// if the file is smaller thant the block
block = size
}
// start with the first block from the end
start := size - block
linesRead := 0
first := int64(-1)
L1:
for {
if start < 0 {
// if the block is bigger than the file start from the beginning
start = 0
}
// read the current blok
buf := make([]byte, block)
_, err := f.ReadAt(buf, start)
if err != nil {
return 0, err
}
L2:
for {
n := bytes.IndexRune(buf, '\n')
switch n {
case -1:
break L2
default:
if first == -1 {
first = start + int64(n)
}
linesRead++
if linesRead >= lines {
break L1
}
buf = buf[n+1:]
continue
}
}
if linesRead >= lines {
break
}
if start == 0 {
// we already read the full file
break
}
// we didn't get all the lines. Start back from the previous block.
start -= block
// reset the first linebreak index
first = -1
}
switch {
case first == -1:
// It doesn't have line breaks, start from the beginning.
first = 0
case linesRead < lines:
// the file has fewer lines than requested
first = 0
}
return first, nil
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment