本文目录导读:

- 方案一:Python + watchdog(推荐)
- 方案二:Bash + rsync + inotify(Linux专用)
- 方案三:Python + watchdog(带配置文件版本)
- 配置文件示例 (sync_config.json)
- 安装依赖
- 使用方法
- 特点
我来为您编写一个目录实时同步脚本,使用Python和watchdog库来实现:
Python + watchdog(推荐)
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
目录实时同步脚本
功能:实时监控源目录变化并同步到目标目录
"""
import os
import shutil
import time
import logging
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
from pathlib import Path
class SyncHandler(FileSystemEventHandler):
"""文件系统事件处理器"""
def __init__(self, source_dir, target_dir):
self.source_dir = Path(source_dir).resolve()
self.target_dir = Path(target_dir).resolve()
self.logger = logging.getLogger('dir_sync')
def get_relative_path(self, path):
"""获取相对路径"""
src_path = Path(path).resolve()
try:
relative = src_path.relative_to(self.source_dir)
return relative
except ValueError:
self.logger.error(f"路径不在源目录中: {path}")
return None
def get_target_path(self, path):
"""获取目标路径"""
relative = self.get_relative_path(path)
if relative is None:
return None
return self.target_dir / relative
def on_modified(self, event):
"""文件或目录修改时触发"""
if event.is_directory:
return
self.logger.info(f"修改: {event.src_path}")
self.sync_file(event.src_path)
def on_created(self, event):
"""文件或目录创建时触发"""
self.logger.info(f"创建: {event.src_path}")
if event.is_directory:
self.create_directory(event.src_path)
else:
self.sync_file(event.src_path)
def on_moved(self, event):
"""文件或目录移动/重命名时触发"""
src_path = event.src_path
dest_path = event.dest_path
self.logger.info(f"移动: {src_path} -> {dest_path}")
# 计算目标路径
src_relative = self.get_relative_path(src_path)
dest_relative = self.get_relative_path(dest_path)
if src_relative is None or dest_relative is None:
return
# 删除旧路径
old_target = self.target_dir / src_relative
# 创建新路径
new_target = self.target_dir / dest_relative
try:
if event.is_directory:
# 移动目录
if old_target.exists():
shutil.rmtree(old_target)
self.create_directory(new_target)
else:
# 移动文件
if old_target.exists():
old_target.unlink()
self.sync_file(dest_path)
except Exception as e:
self.logger.error(f"处理移动事件失败: {e}")
def on_deleted(self, event):
"""文件或目录删除时触发"""
self.logger.info(f"删除: {event.src_path}")
target_path = self.get_target_path(event.src_path)
if target_path is None:
return
try:
if event.is_directory:
if target_path.exists():
shutil.rmtree(target_path)
self.logger.info(f"已删除目录: {target_path}")
else:
if target_path.exists():
target_path.unlink()
self.logger.info(f"已删除文件: {target_path}")
except Exception as e:
self.logger.error(f"删除失败 {target_path}: {e}")
def sync_file(self, src_file):
"""同步单个文件"""
target_path = self.get_target_path(src_file)
if target_path is None:
return
try:
# 确保目标目录存在
target_path.parent.mkdir(parents=True, exist_ok=True)
# 复制文件(保留文件元数据)
src_file_path = Path(src_file)
if src_file_path.exists():
shutil.copy2(src_file_path, target_path)
self.logger.info(f"已同步文件: {target_path}")
except Exception as e:
self.logger.error(f"同步文件失败 {src_file}: {e}")
def create_directory(self, src_dir):
"""创建目录"""
target_path = self.get_target_path(src_dir)
if target_path is None:
return
try:
target_path.mkdir(parents=True, exist_ok=True)
self.logger.info(f"已创建目录: {target_path}")
except Exception as e:
self.logger.error(f"创建目录失败 {target_path}: {e}")
class DirectorySync:
"""目录同步管理器"""
def __init__(self, source_dir, target_dir, log_file=None):
self.source_dir = source_dir
self.target_dir = target_dir
self.observer = None
self.setup_logging(log_file)
def setup_logging(self, log_file=None):
"""配置日志"""
log_format = '%(asctime)s - %(levelname)s - %(message)s'
# 清除现有的handler
for handler in logging.root.handlers[:]:
logging.root.removeHandler(handler)
logging.basicConfig(
level=logging.INFO,
format=log_format,
handlers=[
logging.StreamHandler(), # 控制台输出
]
)
if log_file:
file_handler = logging.FileHandler(log_file, encoding='utf-8')
file_handler.setFormatter(logging.Formatter(log_format))
logging.getLogger().addHandler(file_handler)
def validate_paths(self):
"""验证路径是否有效"""
if not os.path.exists(self.source_dir):
raise ValueError(f"源目录不存在: {self.source_dir}")
if os.path.exists(self.target_dir) and not os.path.isdir(self.target_dir):
raise ValueError(f"目标路径不是目录: {self.target_dir}")
# 创建目标目录
os.makedirs(self.target_dir, exist_ok=True)
def is_same_directory(self):
"""检查源目录和目标目录是否相同"""
return os.path.abspath(self.source_dir) == os.path.abspath(self.target_dir)
def initial_sync(self):
"""初始同步"""
logging.info(f"开始初始同步: {self.source_dir} -> {self.target_dir}")
try:
if self.is_same_directory():
logging.warning("源目录和目标目录相同,不需要同步")
return
# 复制所有文件
for root, dirs, files in os.walk(self.source_dir):
# 计算相对路径
root_relative = os.path.relpath(root, self.source_dir)
target_root = self.target_dir
if root_relative != '.':
target_root = os.path.join(self.target_dir, root_relative)
os.makedirs(target_root, exist_ok=True)
# 复制文件
for file in files:
src_file = os.path.join(root, file)
target_file = os.path.join(target_root, file)
shutil.copy2(src_file, target_file)
logging.info("初始同步完成")
except Exception as e:
logging.error(f"初始同步失败: {e}")
raise
def start(self):
"""启动监控"""
try:
self.validate_paths()
if self.is_same_directory():
raise ValueError("源目录和目标目录不能相同")
# 初始同步
self.initial_sync()
# 创建事件处理器
event_handler = SyncHandler(self.source_dir, self.target_dir)
# 创建观察者
self.observer = Observer()
self.observer.schedule(
event_handler,
self.source_dir,
recursive=True # 递归监控子目录
)
self.observer.start()
logging.info(f"目录同步已启动:")
logging.info(f" 源目录: {self.source_dir}")
logging.info(f" 目标目录: {self.target_dir}")
logging.info("按 Ctrl+C 停止同步")
# 保持程序运行
self.observer.join()
except KeyboardInterrupt:
logging.info("收到中断信号,停止同步...")
self.stop()
except Exception as e:
logging.error(f"启动同步失败: {e}")
exit(1)
def stop(self):
"""停止监控"""
if self.observer:
self.observer.stop()
self.observer.join()
logging.info("目录同步已停止")
def main():
"""主函数"""
import argparse
parser = argparse.ArgumentParser(description='目录实时同步工具')
parser.add_argument('source', help='源目录路径')
parser.add_argument('target', help='目标目录路径')
parser.add_argument('--log', help='日志文件路径(可选)', default=None)
parser.add_argument('--initial-sync', type=bool, default=True,
help='是否执行初始同步(默认: True)')
args = parser.parse_args()
# 创建同步实例
sync_manager = DirectorySync(
source_dir=args.source,
target_dir=args.target,
log_file=args.log
)
# 启动同步
sync_manager.start()
if __name__ == "__main__":
main()
Bash + rsync + inotify(Linux专用)
#!/bin/bash
# 目录实时同步脚本(使用inotifywait和rsync)
SOURCE_DIR="$1"
TARGET_DIR="$2"
LOG_FILE="${3:-/tmp/dir_sync.log}"
if [ -z "$SOURCE_DIR" ] || [ -z "$TARGET_DIR" ]; then
echo "用法: $0 <源目录> <目标目录> [日志文件]"
exit 1
fi
# 检查依赖
if ! command -v inotifywait &> /dev/null; then
echo "Error: 请先安装 inotify-tools"
exit 1
fi
if ! command -v rsync &> /dev/null; then
echo "Error: 请先安装 rsync"
exit 1
fi
# 确保目录存在
mkdir -p "$SOURCE_DIR" "$TARGET_DIR"
# 获取目录的绝对路径
SOURCE_DIR=$(realpath "$SOURCE_DIR")
TARGET_DIR=$(realpath "$TARGET_DIR")
if [ "$SOURCE_DIR" = "$TARGET_DIR" ]; then
echo "Error: 源目录和目标目录不能相同"
exit 1
fi
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" >> "$LOG_FILE"
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1"
}
# 初始同步
log "开始初始同步: $SOURCE_DIR -> $TARGET_DIR"
rsync -avz --delete "$SOURCE_DIR/" "$TARGET_DIR/"
log "初始同步完成"
log "启动目录监控..."
# 监控文件变化
inotifywait -m -r -e create -e modify -e delete -e move \
--format '%e %w%f' \
"$SOURCE_DIR" |
while read event file; do
case "$event" in
CREATE|MODIFY)
log "事件: $event - $file"
rsync -avz --update "$SOURCE_DIR/" "$TARGET_DIR/"
;;
DELETE)
log "事件: DELETE - $file"
rsync -avz --delete "$SOURCE_DIR/" "$TARGET_DIR/"
;;
MOVED_TO|MOVED_FROM)
log "事件: MOVE - $file"
rsync -avz --delete "$SOURCE_DIR/" "$TARGET_DIR/"
;;
*)
rsync -avz --update "$SOURCE_DIR/" "$TARGET_DIR/"
;;
esac
done
Python + watchdog(带配置文件版本)
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
目录同步工具 - 配置文件版本
支持多个目录对同步
"""
import json
import os
import sys
from pathlib import Path
from directory_sync import DirectorySync # 导入上面的类
def load_config(config_file):
"""加载配置文件"""
with open(config_file, 'r', encoding='utf-8') as f:
return json.load(f)
def main():
config_file = 'sync_config.json'
if not os.path.exists(config_file):
# 创建默认配置
default_config = {
"sync_pairs": [
{
"source": "/path/to/source1",
"target": "/path/to/target1",
"enabled": True
},
{
"source": "/path/to/source2",
"target": "/path/to/target2",
"enabled": False
}
],
"log_dir": "logs"
}
with open(config_file, 'w', encoding='utf-8') as f:
json.dump(default_config, f, indent=2)
print(f"已创建默认配置文件: {config_file}")
print("请编辑文件后重新运行程序")
return
# 加载配置
config = load_config(config_file)
# 创建日志目录
log_dir = config.get('log_dir', 'logs')
os.makedirs(log_dir, exist_ok=True)
# 同步所有启用的目录对
sync_managers = []
for pair in config['sync_pairs']:
if not pair.get('enabled', True):
continue
source = pair['source']
target = pair['target']
print(f"配置同步: {source} -> {target}")
# 为每个同步对创建独立的日志
log_file = os.path.join(log_dir, f"sync_{Path(source).name}.log")
try:
sync_manager = DirectorySync(
source_dir=source,
target_dir=target,
log_file=log_file
)
sync_managers.append(sync_manager)
# 创建线程来运行同步(这里简化为逐个运行)
sync_manager.start()
except Exception as e:
print(f"启动同步失败 ({source}): {e}")
if __name__ == "__main__":
main()
配置文件示例 (sync_config.json)
{
"sync_pairs": [
{
"source": "/home/user/documents",
"target": "/backup/documents",
"enabled": true
},
{
"source": "/home/user/photos",
"target": "/backup/photos",
"enabled": true
}
],
"log_dir": "logs",
"sync_interval": 5,
"exclude_patterns": [
"*.tmp",
"*.log",
".git"
]
}
安装依赖
# 方法1:pip安装watchdog(推荐) pip install watchdog # 方法2:如果使用国内镜像 pip install watchdog -i https://pypi.tuna.tsinghua.edu.cn/simple # 方法3:apt安装inotify-tools(Bash方案需要) sudo apt-get install inotify-tools rsync
使用方法
# Python方案使用 python3 directory_sync.py /home/user/documents /backup/documents # 指定日志文件 python3 directory_sync.py /home/user/documents /backup/documents --log /tmp/sync.log # Bash方案使用(需要安装inotify-tools) bash dir_sync.sh /home/user/documents /backup/documents # 配置文件方案 python3 dir_sync_config.py
特点
✅ 实时监控:使用watchdog/inotify实时监听文件变化
✅ 保留元数据:shutil.copy2和rsync保留文件时间戳、权限等
✅ 日志记录:详细记录所有同步操作
✅ 错误处理:完善的异常处理机制
✅ 配置灵活:支持多目录同步、排除模式等
✅ 跨平台:Python版本支持Windows、Linux、macOS
选择合适的方案后,您就可以实现目录的实时同步了!