-
-
Save scorredoira/cb1a564978fca9805f9c3c6c1a86aed0 to your computer and use it in GitHub Desktop.
read a file from the end
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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