golang文件变更时自动执行命令的插件库Watch的使用

Golang文件变更时自动执行命令的插件库Watch的使用

Watch是一个用于监控目录树变化的工具,当文件发生变化时会自动执行指定的命令。默认情况下,输出会显示在acme窗口中,但也可以通过参数配置输出到终端。

基本用法

Watch [-v] [-t] [-p <path>] [-x <regexp>] <command>

参数说明

  • -v:启用详细的调试输出
  • -t:将输出发送到终端而不是acme
  • -p <path>:指定要监控的路径(如果是目录则会递归监控)
  • -x <regexp>:指定用于排除文件和目录的正则表达式

完整示例

下面是一个使用Watch监控Go文件变化并自动运行测试的示例:

// 监控当前目录下所有.go文件的变化,当文件改变时运行go test
// -t 参数表示输出到终端
// -x 参数排除vendor目录
Watch -t -x 'vendor/.*' 'go test ./...'

更复杂的示例

// 监控特定目录(src)下的.go文件变化
// 当文件变化时运行go build和go test
// -v 参数启用详细输出
// -p 参数指定监控路径
// -x 参数排除_test.go文件和tmp目录
Watch -v -p ./src -x '(.*_test\.go|tmp/.*)' 'go build ./... && go test ./...'

注意事项

  1. Watch会递归监控指定目录下的所有文件变化
  2. 可以通过正则表达式排除不需要监控的文件或目录
  3. 命令可以是任何有效的shell命令
  4. 默认输出到acme窗口,使用-t参数可改为输出到终端

这个工具特别适合在开发过程中自动运行测试或构建任务,提高开发效率。


更多关于golang文件变更时自动执行命令的插件库Watch的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于golang文件变更时自动执行命令的插件库Watch的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


使用 fsnotify 实现 Golang 文件变更自动执行命令

在 Golang 中,可以使用 fsnotify 库来监控文件系统变更并触发相应操作。下面我将介绍如何使用 fsnotify 实现文件变更时自动执行命令的功能。

安装 fsnotify

首先安装 fsnotify 库:

go get github.com/fsnotify/fsnotify

基本使用示例

以下是一个简单的示例,当指定文件或目录发生变化时执行命令:

package main

import (
	"fmt"
	"log"
	"os"
	"os/exec"
	"path/filepath"

	"github.com/fsnotify/fsnotify"
)

func main() {
	// 创建新的 watcher
	watcher, err := fsnotify.NewWatcher()
	if err != nil {
		log.Fatal(err)
	}
	defer watcher.Close()

	// 启动 goroutine 处理事件
	done := make(chan bool)
	go func() {
		for {
			select {
			case event, ok := <-watcher.Events:
				if !ok {
					return
				}
				// 只处理写入和创建事件
				if event.Op&fsnotify.Write == fsnotify.Write || event.Op&fsnotify.Create == fsnotify.Create {
					log.Println("检测到变更:", event.Name)
					// 执行命令
					runCommand("echo", "文件已变更:", event.Name)
				}
			case err, ok := <-watcher.Errors:
				if !ok {
					return
				}
				log.Println("错误:", err)
			}
		}
	}()

	// 添加要监控的路径
	path := "./" // 监控当前目录
	err = watcher.Add(path)
	if err != nil {
		log.Fatal(err)
	}

	log.Printf("开始监控目录: %s\n", path)
	<-done
}

// 执行命令的函数
func runCommand(name string, arg ...string) {
	cmd := exec.Command(name, arg...)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	err := cmd.Run()
	if err != nil {
		log.Printf("命令执行失败: %v\n", err)
	}
}

更完善的实现

下面是一个更完善的实现,包含递归监控子目录、过滤特定文件扩展名等功能:

package main

import (
	"fmt"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"time"

	"github.com/fsnotify/fsnotify"
)

type Config struct {
	WatchPath    string   // 监控路径
	Extensions   []string // 监控的文件扩展名
	Command      string   // 要执行的命令
	CommandArgs  []string // 命令参数
	DebounceTime int      // 防抖时间(毫秒)
}

func main() {
	config := Config{
		WatchPath:    ".",
		Extensions:   []string{".go", ".txt"},
		Command:      "echo",
		CommandArgs:  []string{"文件变更:", "%file%"},
		DebounceTime: 500,
	}

	watcher, err := fsnotify.NewWatcher()
	if err != nil {
		log.Fatal(err)
	}
	defer watcher.Close()

	// 递归添加监控目录
	err = addWatchRecursive(watcher, config.WatchPath)
	if err != nil {
		log.Fatal(err)
	}

	// 防抖机制
	var debounceTimer *time.Timer
	lastEventTime := time.Now()

	go func() {
		for {
			select {
			case event, ok := <-watcher.Events:
				if !ok {
					return
				}
				// 检查文件扩展名
				if !hasValidExtension(event.Name, config.Extensions) {
					continue
				}

				// 只处理写入、创建、重命名事件
				if event.Op&fsnotify.Write == fsnotify.Write ||
					event.Op&fsnotify.Create == fsnotify.Create ||
					event.Op&fsnotify.Rename == fsnotify.Rename {

					// 防抖处理
					now := time.Now()
					if debounceTimer != nil {
						debounceTimer.Stop()
					}

					// 设置新的定时器
					debounceTimer = time.AfterFunc(
						time.Duration(config.DebounceTime)*time.Millisecond,
						func() {
							// 替换命令参数中的占位符
							args := make([]string, len(config.CommandArgs))
							for i, arg := range config.CommandArgs {
								args[i] = strings.ReplaceAll(arg, "%file%", event.Name)
							}
							runCommand(config.Command, args...)
						},
					)
					lastEventTime = now
				}

				// 如果是新创建的目录,添加到监控
				if event.Op&fsnotify.Create == fsnotify.Create {
					if stat, err := os.Stat(event.Name); err == nil && stat.IsDir() {
						watcher.Add(event.Name)
					}
				}

			case err, ok := <-watcher.Errors:
				if !ok {
					return
				}
				log.Println("监控错误:", err)
			}
		}
	}()

	log.Printf("开始监控目录: %s (扩展名: %v)\n", config.WatchPath, config.Extensions)
	select {}
}

// 递归添加监控目录
func addWatchRecursive(watcher *fsnotify.Watcher, path string) error {
	err := filepath.Walk(path, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		if info.IsDir() {
			return watcher.Add(path)
		}
		return nil
	})
	return err
}

// 检查文件扩展名是否匹配
func hasValidExtension(filename string, extensions []string) bool {
	if len(extensions) == 0 {
		return true
	}
	ext := filepath.Ext(filename)
	for _, e := range extensions {
		if strings.EqualFold(ext, e) {
			return true
		}
	}
	return false
}

// 执行命令
func runCommand(name string, args ...string) {
	cmd := exec.Command(name, args...)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	log.Printf("执行命令: %s %v\n", name, args)
	err := cmd.Run()
	if err != nil {
		log.Printf("命令执行失败: %v\n", err)
	}
}

使用说明

  1. 基本功能

    • 监控指定目录及其子目录
    • 当文件变更时执行指定命令
    • 支持防抖机制,避免短时间内多次触发
  2. 配置选项

    • WatchPath: 要监控的路径
    • Extensions: 只监控这些扩展名的文件(空数组表示监控所有文件)
    • Command: 要执行的命令
    • CommandArgs: 命令参数,可用 %file% 占位符表示变更的文件路径
    • DebounceTime: 防抖时间(毫秒)
  3. 扩展功能

    • 自动监控新创建的目录
    • 支持递归监控子目录
    • 可以过滤特定扩展名的文件

实际应用示例

假设你想在 .go 文件变更时自动运行测试:

config := Config{
	WatchPath:    ".",
	Extensions:   []string{".go"},
	Command:      "go",
	CommandArgs:  []string{"test", "-v"},
	DebounceTime: 1000,
}

或者想在 Markdown 文件变更时重新生成文档:

config := Config{
	WatchPath:    "./docs",
	Extensions:   []string{".md"},
	Command:      "make",
	CommandArgs:  []string{"docs"},
	DebounceTime: 2000,
}

这个实现提供了灵活的文件监控功能,可以根据实际需求进行调整和扩展。

回到顶部