0

0

使用 inotifywait 监控文件并自动重启 Go 应用的教程

碧海醫心

碧海醫心

发布时间:2025-09-24 13:14:21

|

1035人浏览过

|

来源于php中文网

原创

使用 inotifywait 监控文件并自动重启 Go 应用的教程

本文详细介绍了如何使用 inotifywait 和 Bash 脚本来监控指定目录下的 Go 或 HTML 文件变更,并在文件保存时自动重启 Go 应用程序。教程重点纠正了原始脚本中 grep 命令的错误用法,并强调了避免使用 kill -9 进行进程终止的重要性,推荐使用更优雅的 kill -15,以确保应用程序能够进行清理工作。通过一个优化后的脚本示例,读者将学会构建一个高效且健壮的开发辅助工具

引言:开发中的自动重载需求

go 语言或其他 web 应用的开发过程中,每次修改代码后手动停止并重新启动服务是一个繁琐且低效的过程。为了提高开发效率,自动监控文件变更并触发应用重载的机制变得尤为重要。inotifywait 是 linux 系统下 inotify 工具集的一部分,它能够实时监控文件系统事件,是实现此类自动重载功能的理想选择。本文将基于一个实际案例,详细讲解如何构建一个 bash 脚本,利用 inotifywait 监控 go 和 html 文件,并安全地重启 go 应用程序。

核心工具:inotifywait 简介

inotifywait 是一个命令行工具,用于等待文件系统事件。它可以监控文件或目录的创建、删除、修改、移动等多种事件。其常用参数包括:

  • -m:持续监控模式,不会在第一个事件发生后退出。
  • -r:递归监控子目录。
  • -q:静默模式,只输出事件路径,不输出额外信息。
  • -e :指定要监控的事件类型,例如 close_write(文件关闭写入时,通常表示文件已保存)。

构建自动重载脚本

我们的目标是创建一个 Bash 脚本,它接收一个监控目录和一个 Go 应用程序入口文件作为参数。当监控目录中的 .go 或 .html 文件被保存时,脚本将终止当前运行的 Go 应用程序实例,然后重新编译并启动它。

初始脚本及问题分析

以下是最初的脚本尝试,它展示了基本的逻辑,但也存在一些关键问题:

#!/usr/bin/env bash

WATCH_DIR=$1
FILENAME=$2

function restart_goserver() {
  if go run $FILENAME
  then
    pkill -9 -f $FILENAME > /dev/null 2>&1
    pkill -9 -f a.out > /dev/null 2>&1
    go run $FILENAME &
    echo "started $FILENAME"
  else
    echo "server restart failed"
  fi
}

cd $WATCH_DIR
restart_goserver

echo "watching directory: $WATCH_DIR"
inotifywait -mrq -e close_write $WATCH_DIR | while read file
do
  if grep -E '^(.*\.go)|(.*\.html)$'
  then
    echo "--------------------"
    restart_goserver
  fi
done

此脚本存在两个主要问题:

  1. grep 命令的错误用法:在 while read file 循环中,grep -E '^(.*\.go)|(.*\.html)$' 并没有接收任何输入。read file 命令将监控到的文件路径赋值给变量 file,但 grep 命令需要明确地从标准输入或文件中读取内容。
  2. kill -9 的不当使用:pkill -9 强制终止进程,这可能导致数据丢失或资源未正确释放。在大多数情况下,我们应该优先尝试发送一个更温和的信号,如 SIGTERM。

解决方案一:修正 grep 命令

grep 命令用于在文本中搜索模式。当它没有指定文件参数时,默认会从标准输入读取。在 inotifywait 的输出被 read file 捕获后,要对 file 变量的内容进行模式匹配,需要将其通过管道传递给 grep。

修正后的代码片段:

# ...
inotifywait -mrq -e close_write $WATCH_DIR | while read file
do
  # 将文件路径通过 echo 传递给 grep 进行匹配
  if echo "$file" | grep -E '^(.*\.go)|(.*\.html)$' > /dev/null
  then
    echo "--------------------"
    restart_goserver
  fi
done

这里增加了 > /dev/null 是为了抑制 grep 的输出,我们只关心其退出状态(0 表示匹配成功,非0表示失败)。

解决方案二:优雅地终止进程

kill -9 发送 SIGKILL 信号,这是一个不可捕获、不可忽略的信号,会立即终止进程。这相当于拔掉电源,进程没有机会执行任何清理工作。而 kill -15 发送 SIGTERM 信号,这是一个可捕获的信号,允许进程在终止前执行清理任务,如保存数据、关闭文件句柄、释放网络连接等。

NatAgent
NatAgent

AI数据情报监测与分析平台

下载

修正后的 restart_goserver 函数:

function restart_goserver() {
  # 尝试编译Go程序,如果编译失败则不重启
  echo "Attempting to build $FILENAME..."
  if go build -o app_server $FILENAME # 编译为可执行文件 app_server
  then
    # 查找并发送 SIGTERM 信号给之前启动的进程
    # 使用 pgrep 结合进程名或命令行参数来精确查找进程
    # 注意:pkill -f "$FILENAME" 可能会误杀其他同名进程,更推荐使用 pid 文件或更精确的 pgrep 模式
    # 这里为了演示,我们假设 go run $FILENAME 或 ./app_server 是唯一的

    # 尝试查找之前由 go run 启动的进程
    pkill -15 -f "go run $FILENAME" > /dev/null 2>&1
    # 尝试查找之前编译的 app_server 进程
    pkill -15 -f "./app_server" > /dev/null 2>&1

    # 等待一小段时间,让进程有机会清理并退出
    sleep 1

    echo "Previous server instances terminated (if any)."

    # 启动新的编译后的应用程序
    ./app_server & # 运行编译后的可执行文件
    echo "Started new instance of $FILENAME (as app_server)."
  else
    echo "Server build failed for $FILENAME. Not restarting."
  fi
}

注意事项:

  • 在实际生产环境中,pkill -f 可能会过于宽泛。更稳健的方法是使用 PID 文件来记录应用程序的进程 ID,并在重启时精确地 kill 该 PID。

  • Go 应用程序本身应该包含信号处理逻辑,以捕获 SIGTERM 并执行优雅关机。例如:

    package main
    
    import (
        "fmt"
        "os"
        "os/signal"
        "syscall"
        "time"
    )
    
    func main() {
        // 创建一个通道来接收信号
        sigs := make(chan os.Signal, 1)
        // 注册要接收的信号
        signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
    
        // 启动一个 goroutine 来等待信号
        go func() {
            sig := <-sigs
            fmt.Println()
            fmt.Println("Received signal:", sig)
            // 执行清理工作
            fmt.Println("Performing graceful shutdown...")
            time.Sleep(2 * time.Second) // 模拟清理工作
            fmt.Println("Shutdown complete. Exiting.")
            os.Exit(0)
        }()
    
        // 应用程序的主逻辑
        fmt.Println("Server started. Press Ctrl+C or send SIGTERM to exit.")
        select {} // 阻塞主 goroutine,直到接收到信号
    }

完整的优化脚本

结合上述修正,以下是优化后的 Bash 脚本:

#!/usr/bin/env bash

# 脚本用途:监控指定目录下的 .go 或 .html 文件变更,并自动重启 Go 应用程序。
# 用法:./gowatcher.sh <监控目录> 
# 示例:./gowatcher.sh /path/to/my/project main.go

WATCH_DIR=$1
FILENAME=$2
APP_EXEC_NAME="app_server" # 编译后的可执行文件名

# 检查参数是否提供
if [ -z "$WATCH_DIR" ] || [ -z "$FILENAME" ]; then
  echo "Usage: $0  "
  exit 1
fi

# 确保监控目录存在
if [ ! -d "$WATCH_DIR" ]; then
  echo "Error: Directory '$WATCH_DIR' does not exist."
  exit 1
fi

# 确保Go应用主文件存在
if [ ! -f "$WATCH_DIR/$FILENAME" ]; then
  echo "Error: Go main file '$WATCH_DIR/$FILENAME' does not exist."
  exit 1
fi

# 函数:重启 Go 服务器
function restart_goserver() {
  echo "--------------------"
  echo "Attempting to build and restart $FILENAME..."

  # 1. 尝试编译Go程序
  # 将编译后的可执行文件放在 WATCH_DIR 目录下
  if go build -o "$WATCH_DIR/$APP_EXEC_NAME" "$WATCH_DIR/$FILENAME"
  then
    # 2. 优雅地终止之前运行的实例
    # 注意:这里使用 pkill -f 可能会终止所有包含 APP_EXEC_NAME 字符串的进程
    # 在生产环境或复杂场景下,建议使用 PID 文件或更精确的进程管理方法
    echo "Sending SIGTERM to previous instances of $APP_EXEC_NAME..."
    pkill -15 -f "$APP_EXEC_NAME" > /dev/null 2>&1

    # 等待一小段时间,给进程清理的机会
    sleep 1 

    # 3. 启动新的编译后的应用程序实例
    echo "Starting new instance of $APP_EXEC_NAME..."
    # 使用 nohup 和 & 确保脚本退出后应用也能继续运行,并重定向输出
    nohup "$WATCH_DIR/$APP_EXEC_NAME" > "$WATCH_DIR/app.log" 2>&1 &
    echo "New instance of $APP_EXEC_NAME started. Output redirected to $WATCH_DIR/app.log."
  else
    echo "Go application build failed for $FILENAME. Server not restarted."
  fi
}

# 切换到监控目录,方便 go build 和运行
cd "$WATCH_DIR" || { echo "Failed to change directory to $WATCH_DIR"; exit 1; }

# 首次启动服务器
restart_goserver

echo "Watching directory: $WATCH_DIR for changes in .go or .html files..."

# 使用 inotifywait 监控文件变更
inotifywait -mrq -e close_write "$WATCH_DIR" | while read -r event_path event_type filename
do
  # 检查文件名是否符合 .go 或 .html 模式
  if echo "$filename" | grep -E '\.(go|html)$' > /dev/null
  then
    echo "Detected change in: $filename (Event: $event_type)"
    restart_goserver
  fi
done

使用方法

  1. 将上述脚本保存为 gowatcher.sh 并赋予执行权限:
    chmod +x gowatcher.sh
  2. 运行脚本,传入你的 Go 项目目录和主 Go 文件名:
    ./gowatcher.sh /path/to/your/go/project main.go

    例如:

    ./gowatcher.sh ~/my_go_app server.go
  3. 现在,当你修改 /path/to/your/go/project 目录下的任何 .go 或 .html 文件并保存时,脚本将自动检测到变更并重启你的 Go 应用程序。

注意事项与最佳实践

  • 进程管理:pkill -f 是一种便捷但不够精确的进程查找方式。在更复杂的场景下,考虑使用 PID 文件 (echo $$ > /tmp/my_app.pid) 来精确管理进程,或使用 pgrep -f "command_pattern" 配合 kill。
  • 错误处理:脚本中已包含一些基本的参数和目录检查。在生产环境中,应增加更全面的错误处理和日志记录。
  • Go 应用的信号处理:确保你的 Go 应用程序能够优雅地处理 SIGTERM 信号,进行资源清理后再退出。
  • 性能考量:inotifywait 在监控大量文件或深层目录时可能消耗较多资源。对于超大型项目,可能需要优化监控范围或考虑其他更专业的工具。
  • 平台兼容性:inotifywait 是 Linux 特有的工具。在 macOS 或 Windows 上,需要使用其他文件监控工具(如 fswatch 或特定平台的 API)。
  • 编译与运行分离:脚本中已将 go run 替换为 go build 后运行编译的可执行文件,这通常是更好的实践,因为 go run 每次都会重新编译。
  • 输出重定向:使用 nohup ... > app.log 2>&1 & 可以将应用程序的输出重定向到文件,并确保应用程序在脚本终端关闭后继续运行。

总结

通过本教程,我们学习了如何利用 inotifywait 结合 Bash 脚本,实现 Go 应用程序的自动化文件变更监控和优雅重启。这不仅解决了原始脚本中 grep 命令的错误用法,更重要的是强调了在进程管理中避免使用 kill -9,转而采用 kill -15 进行优雅关机的重要性。掌握这些技巧,将显著提升开发效率,并确保应用程序在重启过程中保持健壮性。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
c语言中null和NULL的区别
c语言中null和NULL的区别

c语言中null和NULL的区别是:null是C语言中的一个宏定义,通常用来表示一个空指针,可以用于初始化指针变量,或者在条件语句中判断指针是否为空;NULL是C语言中的一个预定义常量,通常用来表示一个空值,用于表示一个空的指针、空的指针数组或者空的结构体指针。

236

2023.09.22

java中null的用法
java中null的用法

在Java中,null表示一个引用类型的变量不指向任何对象。可以将null赋值给任何引用类型的变量,包括类、接口、数组、字符串等。想了解更多null的相关内容,可以阅读本专题下面的文章。

458

2024.03.01

while的用法
while的用法

while的用法是“while 条件: 代码块”,条件是一个表达式,当条件为真时,执行代码块,然后再次判断条件是否为真,如果为真则继续执行代码块,直到条件为假为止。本专题为大家提供while相关的文章、下载、课程内容,供大家免费下载体验。

97

2023.09.25

windows查看端口占用情况
windows查看端口占用情况

Windows端口可以认为是计算机与外界通讯交流的出入口。逻辑意义上的端口一般是指TCP/IP协议中的端口,端口号的范围从0到65535,比如用于浏览网页服务的80端口,用于FTP服务的21端口等等。怎么查看windows端口占用情况呢?php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

788

2023.07.26

查看端口占用情况windows
查看端口占用情况windows

端口占用是指与端口关联的软件占用端口而使得其他应用程序无法使用这些端口,端口占用问题是计算机系统编程领域的一个常见问题,端口占用的根本原因可能是操作系统的一些错误,服务器也可能会出现端口占用问题。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

1129

2023.07.27

windows照片无法显示
windows照片无法显示

当我们尝试打开一张图片时,可能会出现一个错误提示,提示说"Windows照片查看器无法显示此图片,因为计算机上的可用内存不足",本专题为大家提供windows照片无法显示相关的文章,帮助大家解决该问题。

803

2023.08.01

windows查看端口被占用的情况
windows查看端口被占用的情况

windows查看端口被占用的情况的方法:1、使用Windows自带的资源监视器;2、使用命令提示符查看端口信息;3、使用任务管理器查看占用端口的进程。本专题为大家提供windows查看端口被占用的情况的相关的文章、下载、课程内容,供大家免费下载体验。

454

2023.08.02

windows无法访问共享电脑
windows无法访问共享电脑

在现代社会中,共享电脑是办公室和家庭的重要组成部分。然而,有时我们可能会遇到Windows无法访问共享电脑的问题。这个问题可能会导致数据无法共享,影响工作和生活的正常进行。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

2355

2023.08.08

C++ 设计模式与软件架构
C++ 设计模式与软件架构

本专题深入讲解 C++ 中的常见设计模式与架构优化,包括单例模式、工厂模式、观察者模式、策略模式、命令模式等,结合实际案例展示如何在 C++ 项目中应用这些模式提升代码可维护性与扩展性。通过案例分析,帮助开发者掌握 如何运用设计模式构建高质量的软件架构,提升系统的灵活性与可扩展性。

0

2026.01.30

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
PostgreSQL 教程
PostgreSQL 教程

共48课时 | 8.1万人学习

Git 教程
Git 教程

共21课时 | 3.1万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号