go linux监测文件变化
文件改变内容有两种方式,效果一样,但执行方式有区别:
- 直接打开文件改,现在很多编辑器都是这样操作的
- 先删除原来的,再新创建写入一个替代原来的。比如vi/vim.这种方式会打断linux inotify原有的监测(就好比你有一部手机,一个人走过来给你砸了,再递给你一部同型号配置,内容备份了你原来的手机,但是新下载了个app。这时候你使用上用不出差别,但是它已经不是你原来的手机了).遇到这种情况时我们需要判断是否文件还存在,存在说明就是上述情况,然后需要重新监听文件状态
go linux监测文件变化,靠系统调用inotify.
演示代码
package mainimport ("errors""fmt""log""os""os/signal""strings""sync""syscall""unsafe"
)type FileLink struct {c chan int64filename string
}
type FileWatcher struct {linkmap map[int32]FileLinklink map[string]int32resource_mu sync.Mutexfd 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] = finfofmt.Println("rewatch " + finfo.filename + " finished")return true
}func Status2String(status uint32) string {var ans []stringif 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 []errorchanlist []<-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 FileWatcherfilewatcher.linkmap = make(map[int32]FileLink)filewatcher.link = make(map[string]int32)var err errorfilewatcher.fd, err = syscall.InotifyInit()if err != nil {return nil, err}go func() {var (buff []byte = make([]byte, 1<<16)size interr errorevent *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_IGNOREDif 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 int64chlist, err = fw.AddWatch("test.txt")if err == nil {ch := make(chan os.Signal, 1)signal.Notify(ch, os.Interrupt)go func() {<-chfw.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含义。