go linux监测文件变化

本文介绍了如何使用Golang通过系统调用inotify来监测Linux文件的变化,包括修改和关闭写入事件。通过FileWatcher结构实现了添加、删除和重新监听文件状态的功能,以及一个简单的示例演示了如何在VSCode和Vim中应用这一功能。
摘要由CSDN通过智能技术生成

go linux监测文件变化

文件改变内容有两种方式,效果一样,但执行方式有区别:

  1. 直接打开文件改,现在很多编辑器都是这样操作的
  2. 先删除原来的,再新创建写入一个替代原来的。比如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含义。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值