Skip to content

Instantly share code, notes, and snippets.

@hiroakis
Created January 15, 2017 17:14
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save hiroakis/f1ef0670ce75c2af56e2232eb7c15daf to your computer and use it in GitHub Desktop.
Save hiroakis/f1ef0670ce75c2af56e2232eb7c15daf to your computer and use it in GitHub Desktop.
The tail command implementation written in go. The command support -F option. It is default action.
package main
import (
"flag"
"fmt"
"io"
"os"
"time"
"github.com/fsnotify/fsnotify"
)
const (
OK int = iota
NG
)
const (
InitialPrintLine = 10
DelimNewLine = "\n"
)
type Tail struct {
filename string
f *os.File
watcher *fsnotify.Watcher
pos int64
}
func NewTail(filename string) (*Tail, error) {
f, err := os.Open(filename)
if err != nil {
return nil, err
}
b := make([]byte, 1)
offset := int64(-1)
numLineFromLastLine := 0
startPos := int64(0)
for {
p, _ := f.Seek(offset, os.SEEK_END)
n, err := f.Read(b)
if err != nil {
if err == io.EOF {
startPos = 0
break
}
return nil, err
}
if p == 0 {
startPos = 0
break
}
if string(b) == DelimNewLine {
numLineFromLastLine++
if numLineFromLastLine == InitialPrintLine+1 {
startPos = p + 1
break
}
}
offset = offset - int64(n)
}
watcher, err := fsnotify.NewWatcher()
if err != nil {
return nil, err
}
err = watcher.Add(filename)
if err != nil {
return nil, err
}
tail := &Tail{
filename: filename,
f: f,
watcher: watcher,
pos: startPos,
}
return tail, err
}
func (self *Tail) Reopen() error {
self.f.Close()
for {
time.Sleep(1 * time.Second)
f, err := os.Open(self.filename)
if err != nil {
if os.IsNotExist(err) {
continue
} else {
return err
}
} else {
self.f = f
self.pos, err = self.f.Seek(0, os.SEEK_SET)
if err != nil {
return err
}
err = self.watcher.Add(self.filename)
if err != nil {
return err
}
break
}
}
return nil
}
func (self *Tail) Start() error {
newLineCh := make(chan bool)
renameCh := make(chan bool)
errCh := make(chan error)
go func() {
for {
select {
case event := <-self.watcher.Events:
if event.Op&fsnotify.Write == fsnotify.Write {
newLineCh <- true
}
if event.Op&fsnotify.Rename == fsnotify.Rename {
renameCh <- true
}
case err := <-self.watcher.Errors:
errCh <- err
}
}
}()
_, err := self.f.Seek(self.pos, os.SEEK_SET)
if err != nil {
return err
}
buf := make([]byte, 1)
for {
for {
n, err := self.f.Read(buf)
if err != nil {
if err == io.EOF {
break
} else {
return err
}
}
if n == 0 {
break
}
fmt.Printf(string(buf)) // print
}
select {
case <-newLineCh:
continue
case <-renameCh:
if err := self.Reopen(); err != nil {
return err
}
}
}
return <-errCh
}
func main() {
flag.Usage = func() {
fmt.Fprintln(os.Stderr, "usage: tailf [file]")
flag.PrintDefaults()
}
flag.Parse()
exitWithMessage := func(code int, message string) {
fmt.Println(message)
os.Exit(code)
}
if len(os.Args) != 2 {
flag.Usage()
os.Exit(NG)
}
filename := os.Args[1]
_, err := os.Stat(filename)
if err != nil {
msg := fmt.Sprintf("%s: No such file or directory", filename)
exitWithMessage(NG, msg)
}
tail, err := NewTail(filename)
if err != nil {
exitWithMessage(NG, err.Error())
}
err = tail.Start()
if err != nil {
exitWithMessage(NG, err.Error())
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment