本文目录导读:

Python 实现完整文件快照
#!/usr/bin/env python3
# file_snapshot.py - 文件快照工具
import os
import hashlib
import json
import time
import argparse
from datetime import datetime
from pathlib import Path
class FileSnapshot:
def __init__(self, base_path, exclude_patterns=None):
self.base_path = Path(base_path)
self.exclude_patterns = exclude_patterns or ['.git', 'node_modules', '__pycache__', '.DS_Store']
self.snapshot_data = {}
def compute_hash(self, file_path, block_size=65536):
"""计算文件哈希值"""
sha256 = hashlib.sha256()
try:
with open(file_path, 'rb') as f:
while chunk := f.read(block_size):
sha256.update(chunk)
return sha256.hexdigest()
except (IOError, PermissionError) as e:
return f"ERROR: {str(e)}"
def get_file_info(self, file_path):
"""获取文件详细信息"""
stat = file_path.stat()
return {
'path': str(file_path.relative_to(self.base_path)),
'size': stat.st_size,
'mtime': stat.st_mtime,
'hash': self.compute_hash(file_path),
'permissions': oct(stat.st_mode & 0o777)
}
def create_snapshot(self):
"""创建文件快照"""
for root, dirs, files in os.walk(self.base_path):
# 排除特定目录
dirs[:] = [d for d in dirs if d not in self.exclude_patterns]
root_path = Path(root)
for file in files:
file_path = root_path / file
if file not in self.exclude_patterns:
try:
file_info = self.get_file_info(file_path)
self.snapshot_data[file_info['path']] = file_info
except Exception as e:
print(f"Error processing {file_path}: {e}")
return {
'name': 'file_snapshot',
'created_at': datetime.now().isoformat(),
'base_path': str(self.base_path),
'file_count': len(self.snapshot_data),
'files': self.snapshot_data
}
def save_snapshot(self, output_file):
"""保存快照到文件"""
snapshot = self.create_snapshot()
with open(output_file, 'w', encoding='utf-8') as f:
json.dump(snapshot, f, indent=2, ensure_ascii=False)
print(f"Snapshot saved to {output_file}")
return snapshot
def compare_snapshots(self, old_snapshot_file, new_snapshot_file=None):
"""比较两个快照"""
with open(old_snapshot_file, 'r', encoding='utf-8') as f:
old_snapshot = json.load(f)
if new_snapshot_file:
with open(new_snapshot_file, 'r', encoding='utf-8') as f:
new_snapshot = json.load(f)
else:
new_snapshot = self.create_snapshot()
changes = {
'added': [],
'modified': [],
'removed': []
}
old_files = old_snapshot['files']
new_files = new_snapshot['files']
# 检查新增和修改的文件
for file_path, file_info in new_files.items():
if file_path not in old_files:
changes['added'].append(file_path)
elif file_info['hash'] != old_files[file_path]['hash']:
changes['modified'].append({
'file': file_path,
'old_size': old_files[file_path]['size'],
'new_size': file_info['size'],
'old_mtime': old_files[file_path]['mtime'],
'new_mtime': file_info['mtime']
})
# 检查删除的文件
for file_path in old_files:
if file_path not in new_files:
changes['removed'].append(file_path)
return changes
def restore_from_snapshot(self, snapshot_file, target_dir):
"""从快照恢复到目标目录"""
with open(snapshot_file, 'r', encoding='utf-8') as f:
snapshot = json.load(f)
target_path = Path(target_dir)
target_path.mkdir(parents=True, exist_ok=True)
for file_path, file_info in snapshot['files'].items():
destino = target_path / file_path
source = self.base_path / file_path
if source.exists():
destino.parent.mkdir(parents=True, exist_ok=True)
import shutil
shutil.copy2(source, destino)
print(f"Restored: {file_path}")
# 命令行接口
def main():
parser = argparse.ArgumentParser(description='文件快照工具')
parser.add_argument('action', choices=['create', 'compare', 'restore', 'monitor'],
help='操作类型')
parser.add_argument('--path', required=True, help='要快照的目录路径')
parser.add_argument('--output', help='快照文件路径')
parser.add_argument('--old-snapshot', help='旧快照文件(用于比较)')
parser.add_argument('--target', help='恢复目标目录')
args = parser.parse_args()
snapshotter = FileSnapshot(args.path)
if args.action == 'create':
output = args.output or f'snapshot_{datetime.now().strftime("%Y%m%d_%H%M%S")}.json'
snapshotter.save_snapshot(output)
elif args.action == 'compare':
if not args.old_snapshot:
print("需要指定 --old-snapshot 参数")
return
changes = snapshotter.compare_snapshots(args.old_snapshot)
print(f"新增文件: {len(changes['added'])}")
print(f"修改文件: {len(changes['modified'])}")
print(f"删除文件: {len(changes['removed'])}")
elif args.action == 'restore':
if not args.old_snapshot or not args.target:
print("需要指定 --old-snapshot 和 --target 参数")
return
snapshotter.restore_from_snapshot(args.old_snapshot, args.target)
elif args.action == 'monitor':
# 监控模式
import time
print(f"监控 {args.path} ... (Ctrl+C 停止)")
snap1 = snapshotter.create_snapshot()
try:
while True:
time.sleep(5)
snap2 = snapshotter.create_snapshot()
if snap1['files'] != snap2['files']:
print(f"[{datetime.now().strftime('%H:%M:%S')}] 文件发生变化")
snap1 = snap2
except KeyboardInterrupt:
print("\n监控停止")
if __name__ == '__main__':
main()
Bash Shell 脚本版本(简单快速)
#!/bin/bash
# file_snapshot.sh - 简单文件快照
# 配置文件
SNAPSHOT_DIR="./snapshots"
LOG_FILE="./snapshot.log"
# 初始化
mkdir -p "$SNAPSHOT_DIR"
timestamp=$(date +"%Y%m%d_%H%M%S")
snapshot_file="$SNAPSHOT_DIR/snapshot_$timestamp.txt"
# 生成 MD5 校验清单
echo "生成文件快照: $snapshot_file"
echo "[$timestamp] 开始生成快照" >> "$LOG_FILE"
# 遍历文件并计算哈希
find . -type f ! -path "*/node_modules/*" ! -path "*/.git/*" | while read -r file; do
md5=$(md5sum "$file" | cut -d' ' -f1)
size=$(stat -f%z "$file" 2>/dev/null || stat -c%s "$file")
echo "$md5|$size|$file" >> "$snapshot_file"
done
echo "快照完成"
echo "[$timestamp] 快照完成: $snapshot_file" >> "$LOG_FILE"
# 比较函数
compare_snapshots() {
old_snapshot=$1
new_snapshot=$2
if [ ! -f "$old_snapshot" ] || [ ! -f "$new_snapshot" ]; then
echo "错误:快照文件不存在"
exit 1
fi
echo "新增文件:"
comm -13 <(cut -d'|' -f3 "$old_snapshot" | sort) <(cut -d'|' -f3 "$new_snapshot" | sort)
echo "删除文件:"
comm -23 <(cut -d'|' -f3 "$old_snapshot" | sort) <(cut -d'|' -f3 "$new_snapshot" | sort)
echo "修改文件:"
# 类似文件但MD5不同
grep -f <(cut -d'|' -f3 "$old_snapshot") "$new_snapshot" | sort > /tmp/new_sorted.txt
grep -f <(cut -d'|' -f3 "$new_snapshot") "$old_snapshot" | sort > /tmp/old_sorted.txt
diff /tmp/old_sorted.txt /tmp/new_sorted.txt | grep "^[<>]" | grep -v "^\*\*\*"
}
# 主菜单
case "$1" in
create)
;;
compare)
if [ $# -eq 3 ]; then
compare_snapshots "$2" "$3"
else
echo "用法: $0 compare <旧快照> <新快照>"
fi
;;
*)
echo "用法: $0 {create|compare <old> <new>}"
;;
esac
PowerShell 脚本(Windows)
# file_snapshot.ps1 - Windows 文件快照
param(
[string]$Path = ".",
[string]$Action = "create",
[string]$OutputFile = "",
[string]$ComparisonFile = ""
)
class FileSnapshot {
[string]$Path
[string]$Timestamp
[int]$FileCount = 0
[hashtable]$Files = @{}
FileSnapshot([string]$path) {
$this.Path = $path
$this.Timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
}
[void]Create() {
Get-ChildItem $this.Path -Recurse -File -Exclude *.dll,*.exe | ForEach-Object {
try {
$hash = Get-FileHash $_.FullName -Algorithm MD5
$relativePath = $_.FullName.Replace((Resolve-Path $this.Path).Path + "\", "")
$this.Files[$relativePath] = @{
Hash = $hash.Hash
Size = $_.Length
Modified = $_.LastWriteTime.Ticks
FullPath = $_.FullName
}
$this.FileCount++
} catch {}
}
}
[string]ToJson() {
$this | ConvertTo-Json -Depth 3
}
[hashtable]Compare([FileSnapshot]$other) {
$result = @{
Added = @()
Modified = @()
Removed = @()
}
# 检查新增和修改
foreach ($file in $other.Files.Keys) {
if (-not $this.Files.ContainsKey($file)) {
$result.Added += $file
} elseif ($this.Files[$file].Hash -ne $other.Files[$file].Hash) {
$result.Modified += $file
}
}
# 检查删除
foreach ($file in $this.Files.Keys) {
if (-not $other.Files.ContainsKey($file)) {
$result.Removed += $file
}
}
return $result
}
[void]Save([string]$filename) {
$json = $this.ToJson()
$filename = $filename -replace '[^a-zA-Z0-9\-_\.]', '_'
$filename = "snapshot_$(Get-Date -Format 'yyyyMMdd_HHmmss').json"
$json | Out-File $filename -Encoding utf8
Write-Host "快照已保存: $filename"
}
static [FileSnapshot]Load([string]$filename) {
$json = Get-Content $filename -Raw
return $json | ConvertFrom-Json
}
}
# 主程序
switch ($Action) {
"create" {
$snapshot = [FileSnapshot]::new((Resolve-Path $Path).Path)
$snapshot.Create()
$snapshot.Save($OutputFile)
Write-Host "创建快照完成: $((Resolve-Path $Path).Path) - $($snapshot.FileCount) 个文件"
}
"compare" {
if ($ComparisonFile) {
$oldSnapshot = [FileSnapshot]::Load($ComparisonFile)
$newSnapshot = [FileSnapshot]::new((Resolve-Path $Path).Path)
$newSnapshot.Create()
$changes = $oldSnapshot.Compare($newSnapshot)
Write-Host "=== 文件变化分析 ==="
Write-Host "新增文件: $($changes.Added.Count)"
$changes.Added | ForEach-Object { Write-Host " + $_" }
Write-Host "修改文件: $($changes.Modified.Count)"
$changes.Modified | ForEach-Object { Write-Host " ~ $_" }
Write-Host "删除文件: $($changes.Removed.Count)"
$changes.Removed | ForEach-Object { Write-Host " - $_" }
} else {
Write-Error "比较模式需要指定 ComparisonFile 参数"
}
}
"monitor" {
Write-Host "开始监控 $Path ... (Ctrl+C 停止)"
$firstRun = $true
$oldSnapshot = $null
while ($true) {
$newSnapshot = [FileSnapshot]::new((Resolve-Path $Path).Path)
$newSnapshot.Create()
if ($firstRun) {
$oldSnapshot = $newSnapshot
$firstRun = $false
} else {
$changes = $oldSnapshot.Compare($newSnapshot)
if (($changes.Added.Count + $changes.Modified.Count + $changes.Removed.Count) -gt 0) {
Write-Host "[$(Get-Date -Format 'HH:mm:ss')] 检测到文件变化:"
$changes.Added | ForEach-Object { Write-Host " + 新增: $_" }
$changes.Modified | ForEach-Object { Write-Host " ~ 修改: $_" }
$changes.Removed | ForEach-Object { Write-Host " - 删除: $_" }
}
$oldSnapshot = $newSnapshot
}
Start-Sleep -Seconds 5
}
}
}
使用说明
Python 版本(推荐)
# 创建快照 python file_snapshot.py create --path /your/directory --output snapshot.json # 比较快照 python file_snapshot.py compare --path /your/directory --old-snapshot old.json # 恢复快照 python file_snapshot.py restore --path /source/directory --old-snapshot snapshot.json --target /restore/directory # 实时监控 python file_snapshot.py monitor --path /your/directory
Bash 版本
# 创建快照 ./file_snapshot.sh create # 比较两个快照 ./file_snapshot.sh compare old_snapshot.txt new_snapshot.txt
PowerShell 版本
# 创建快照 .\file_snapshot.ps1 -Path "C:\your\directory" -Action create # 比较快照 .\file_snapshot.ps1 -Path "C:\your\directory" -Action compare -ComparisonFile "snapshot_20240101_120000.json" # 实时监控 .\file_snapshot.ps1 -Path "C:\your\directory" -Action monitor
这些脚本都能实现基本的文件快照功能,Python版本功能最完整,支持安全、修改、恢复等功能;Bash版本适合Linux/Mac系统快速使用;PowerShell适合Windows环境,选择哪个取决于你的具体需求和使用环境。