Created
January 15, 2017 17:14
-
-
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.
This file contains hidden or 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
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