
在 go 语言或其他 web 应用的开发过程中,每次修改代码后手动停止并重新启动服务是一个繁琐且低效的过程。为了提高开发效率,自动监控文件变更并触发应用重载的机制变得尤为重要。inotifywait 是 linux 系统下 inotify 工具集的一部分,它能够实时监控文件系统事件,是实现此类自动重载功能的理想选择。本文将基于一个实际案例,详细讲解如何构建一个 bash 脚本,利用 inotifywait 监控 go 和 html 文件,并安全地重启 go 应用程序。
inotifywait 是一个命令行工具,用于等待文件系统事件。它可以监控文件或目录的创建、删除、修改、移动等多种事件。其常用参数包括:
我们的目标是创建一个 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此脚本存在两个主要问题:
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 信号,这是一个可捕获的信号,允许进程在终止前执行清理任务,如保存数据、关闭文件句柄、释放网络连接等。
修正后的 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 <监控目录> <Go应用主文件>
# 示例:./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 <directory_to_watch> <golang_main_file>"
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
donechmod +x gowatcher.sh
./gowatcher.sh /path/to/your/go/project main.go
例如:
./gowatcher.sh ~/my_go_app server.go
通过本教程,我们学习了如何利用 inotifywait 结合 Bash 脚本,实现 Go 应用程序的自动化文件变更监控和优雅重启。这不仅解决了原始脚本中 grep 命令的错误用法,更重要的是强调了在进程管理中避免使用 kill -9,转而采用 kill -15 进行优雅关机的重要性。掌握这些技巧,将显著提升开发效率,并确保应用程序在重启过程中保持健壮性。
以上就是使用 inotifywait 监控文件并自动重启 Go 应用的教程的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号