本文目录导读:

- 方案一: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
选择合适的方案后,您就可以实现目录的实时同步了!
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播