go linux监测文件变化
文件改变内容有两种方式,效果一样,但执行方式有区别:
- 直接打开文件改,现在很多编辑器都是这样操作的
- 先删除原来的,再新创建写入一个替代原来的。比如vi/vim.这种方式会打断linux inotify原有的监测(就好比你有一部手机,一个人走过来给你砸了,再递给你一部同型号配置,内容备份了你原来的手机,但是新下载了个app。这时候你使用上用不出差别,但是它已经不是你原来的手机了).遇到这种情况时我们需要判断是否文件还存在,存在说明就是上述情况,然后需要重新监听文件状态
go linux监测文件变化,靠系统调用inotify.
演示代码
package main
import (
"errors"
"fmt"
"log"
"os"
"os/signal"
"strings"
"sync"
"syscall"
"unsafe"
)
type FileLink struct {
c chan int64
filename string
}
type FileWatcher struct {
linkmap map[int32]FileLink
link map[string]int32
resource_mu sync.Mutex
fd int
}
const WATCH_FLAG = syscall.IN_MODIFY | syscall.IN_CLOSE_WRITE
// 重新监听文件状态
func rewatch(fw *FileWatcher, wd *syscall.InotifyEvent) bool {
finfo := fw.linkmap[wd.Wd]
if _, err := os.Stat(finfo.filename); err != nil {
fmt.Fprintln(os.Stderr, "not found "+finfo.filename+" "+err.Error())
return false
}
fw.resource_mu.Lock()
defer fw.resource_mu.Unlock()
delete(fw.linkmap, wd.Wd)
fmt.Println("rewatch " + finfo.filename)
wid, err := syscall.InotifyAddWatch(fw.fd, finfo.filename, WATCH_FLAG)
if wid < 0 {
fmt.Fprintln(os.Stderr, "rewatch failed "+err.Error())
return false
}
wd.Wd = int32(wid)
fw.linkmap[wd.Wd] = finfo
fmt.Println("rewatch " + finfo.filename + " finished")
return true
}
func Status2String(status uint32) string {
var ans []string
if status&syscall.IN_MODIFY > 0 {
ans = append(ans, "modify")
}
if status&syscall.IN_CLOSE_WRITE > 0 {
ans = append(ans, "close_write")
}
return strings.Join(ans, " ")
}
func (s *FileWatcher) AddWatch(filename_list ...string) ([]<-chan int64, error) {
s.resource_mu.Lock()
defer s.resource_mu.Unlock()
var (
errlist []error
chanlist []<-chan int64
)
for _, name := range filename_list {
wd, err := syscall.InotifyAddWatch(s.fd, name, WATCH_FLAG)
if err != nil {
errlist = append(errlist, err)
} else {
s.linkmap[int32(wd)] = FileLink{c: make(chan int64, 1), filename: name}
s.link[name] = int32(wd)
chanlist = append(chanlist, s.linkmap[int32(wd)].c)
}
}
if len(errlist) > 0 {
return chanlist, errors.Join(errlist...)
}
return chanlist, nil
}
func (s *FileWatcher) Delete(filename_list ...string) {
s.resource_mu.Lock()
defer s.resource_mu.Unlock()
for _, name := range filename_list {
if wd, ok := s.link[name]; ok {
syscall.InotifyRmWatch(s.fd, uint32(wd))
close(s.linkmap[wd].c)
delete(s.link, name)
delete(s.linkmap, wd)
}
}
}
func (s *FileWatcher) Close() error {
return syscall.Close(s.fd)
}
func NewFileWatcher() (*FileWatcher, error) {
var filewatcher FileWatcher
filewatcher.linkmap = make(map[int32]FileLink)
filewatcher.link = make(map[string]int32)
var err error
filewatcher.fd, err = syscall.InotifyInit()
if err != nil {
return nil, err
}
go func() {
var (
buff []byte = make([]byte, 1<<16)
size int
err error
event *syscall.InotifyEvent
)
for {
size, err = syscall.Read(filewatcher.fd, buff)
if err == nil {
if size < syscall.SizeofInotifyEvent {
continue
}
//读取到的是inotifyevent 事件数组
for i := 0; i <= size-syscall.SizeofInotifyEvent; i += syscall.SizeofInotifyEvent {
event = (*syscall.InotifyEvent)(unsafe.Pointer(&buff[i]))
if event.Mask&syscall.IN_IGNORED > 0 {
if rewatch(&filewatcher, event) {
event.Mask -= syscall.IN_IGNORED
if event.Mask&syscall.IN_MODIFY == 0 {
event.Mask |= syscall.IN_MODIFY
}
} else {
continue
}
}
filewatcher.linkmap[event.Wd].c <- int64(event.Mask)
// fmt.Println(filewatcher.linkmap[event.Wd].filename + " status " + Status2String(event.Mask))
}
} else {
break
}
}
}()
return &filewatcher, nil
}
func main() {
fw, err := NewFileWatcher()
if err == nil {
var chlist []<-chan int64
chlist, err = fw.AddWatch("test.txt")
if err == nil {
ch := make(chan os.Signal, 1)
signal.Notify(ch, os.Interrupt)
go func() {
<-ch
fw.Close()
os.Exit(0)
}()
for {
status := <-chlist[0]
fmt.Println("test.txt status " + Status2String(uint32(status)))
}
} else {
fw.Close()
}
}
if err != nil {
log.Fatalln(err.Error())
}
}
效果展示
监测vscode 更改
监测vim更改识别
需要注意的点
这里我演示只演示了修改和关闭并写入(保存),如果需要监测其它状态,syscall.IN_开头的就是mask。每个值的作用定量名已经很明确了,不明白自行google含义。