inotifywait未等待文件操作完成的魔兽3地图同步脚本问题
问题
通过Lutris安装Warcraft3后,编写了自动复制Downloads目录中自定义地图(*.w3x)到游戏地图目录的脚本。但遇到问题:inotifywait在下载的0字节文件刚创建时就触发复制操作,导致复制空文件却提示成功。
需要实现无需sleep的文件写入/下载/移动完成等待逻辑(无法预知文件大小和下载速度,不愿用sleep)。曾尝试监听close_write事件,但未解决问题——要么没等待文件关闭写入,要么下载操作不触发该事件。
原脚本如下:
#!/bin/bash # 脚本启动后监听Downloads目录,发现*.w3x文件时,检查游戏地图目录是否存在该文件,不存在则复制并删除原文件 # 监听目录 DIR_TO_WATCH="$HOME/Downloads/" # 目标目录 DEST_DIR="$HOME/Games/battlenet/drive_c/users/user/Documents/Warcraft III/Maps/Download/" # 启动inotifywait监听close事件,管道传递给后续处理 inotifywait -m "$DIR_TO_WATCH" -e close | while read path action file do # 检查是否为War3地图文件 if [[ "$file" == *.w3x ]]; then # 检查目标目录是否已存在该文件 if [ ! -f "$DEST_DIR/$file" ]; then echo "NEW TRANSACTION ---$(date)---" >> wc3_mapsync.log echo "$(date) | $file size before copy: $(stat -c%s "$path$file")" >> wc3_mapsync.log cp "$path$file" "$DEST_DIR" if [ $? -eq 0 ] then echo "$(date) | $file COPIED SUCCESSFULLY $path$file >>> $DEST_DIR" >> wc3_mapsync.log echo "$(date) | $file size after copy: $(stat -c%s "$DEST_DIR/$file")" >> wc3_mapsync.log echo "---END TRANSACTION---" >> wc3_mapsync.log rm "$path$file" else echo "$(date) | $file COPY FAILED with exit status code $? '|' $path$file" >> wc3_mapsync.log echo "---END TRANSACTION---" >> wc3_mapsync.log fi else echo "$(date) | $file ALREADY EXISTS, copy would be duplicate." >> wc3_mapsync.log echo "---END TRANSACTION---" >> wc3_mapsync.log fi else : fi done
解决方案
1. 调整监听事件组合
很多下载工具会先创建临时文件写入,完成后重命名/移动到Downloads目录;或者持续写入直到完成后关闭文件。因此需要同时监听close_write和moved_to事件,覆盖这两种场景:
inotifywait -m "$DIR_TO_WATCH" -e close_write,moved_to
2. 验证文件大小稳定
即使触发了上述事件,仍可能存在文件未完全写入的情况(比如某些工具的写入逻辑)。可以在触发事件后,循环检查文件大小,直到连续两次检查的大小一致,确认文件写入完成:
wait_for_file() { local file="$1" local prev_size=0 local curr_size=$(stat -c%s "$file") while [ "$curr_size" -ne "$prev_size" ]; do prev_size="$curr_size" sleep 0.1 # 短间隔,不影响效率 curr_size=$(stat -c%s "$file") done }
3. 改进事件解析(可选)
使用--format参数让inotifywait输出更结构化的内容,避免read解析错误:
inotifywait -m "$DIR_TO_WATCH" -e close_write,moved_to --format '%w%f %e' | while read full_path events do file=$(basename "$full_path") # 后续处理逻辑 done
修改后的完整脚本
#!/bin/bash # 脚本启动后监听Downloads目录,发现*.w3x文件时,检查游戏地图目录是否存在该文件,不存在则复制并删除原文件 # 监听目录 DIR_TO_WATCH="$HOME/Downloads/" # 目标目录 DEST_DIR="$HOME/Games/battlenet/drive_c/users/user/Documents/Warcraft III/Maps/Download/" # 等待文件写入完成的函数:检查文件大小稳定 wait_for_file() { local file="$1" if [ ! -f "$file" ]; then return 1 fi local prev_size=0 local curr_size=$(stat -c%s "$file") while [ "$curr_size" -ne "$prev_size" ]; do prev_size="$curr_size" sleep 0.1 curr_size=$(stat -c%s "$file") done } # 启动inotifywait监听close_write和moved_to事件,结构化输出 inotifywait -m "$DIR_TO_WATCH" -e close_write,moved_to --format '%w%f %e' | while read full_path events do file=$(basename "$full_path") # 检查是否为War3地图文件 if [[ "$file" == *.w3x ]]; then # 等待文件完全写入 if ! wait_for_file "$full_path"; then echo "$(date) | $file does not exist, skipping." >> wc3_mapsync.log echo "---END TRANSACTION---" >> wc3_mapsync.log continue fi # 检查目标目录是否已存在该文件 if [ ! -f "$DEST_DIR/$file" ]; then echo "NEW TRANSACTION ---$(date)---" >> wc3_mapsync.log echo "$(date) | $file size before copy: $(stat -c%s "$full_path")" >> wc3_mapsync.log cp "$full_path" "$DEST_DIR" if [ $? -eq 0 ] then echo "$(date) | $file COPIED SUCCESSFULLY $full_path >>> $DEST_DIR" >> wc3_mapsync.log echo "$(date) | $file size after copy: $(stat -c%s "$DEST_DIR/$file")" >> wc3_mapsync.log echo "---END TRANSACTION---" >> wc3_mapsync.log rm "$full_path" else echo "$(date) | $file COPY FAILED with exit status code $? '|' $full_path" >> wc3_mapsync.log echo "---END TRANSACTION---" >> wc3_mapsync.log fi else echo "$(date) | $file ALREADY EXISTS, copy would be duplicate." >> wc3_mapsync.log echo "---END TRANSACTION---" >> wc3_mapsync.log fi fi done
内容的提问来源于stack exchange,提问作者hori
相关产品推荐
相关产品推荐

