24有时候在做源码项目二开时,会遇到这种情况:
新项目里陆陆续续加了一堆文件,需要同步到旧项目目录,但已经记不清究竟多了哪些文件,一个一个翻既浪费时间又容易漏。
这时候,用这个 文件对比校验复制工具 就比较省事了:先在新项目里扫一遍生成路径清单,再拿旧项目做存在性校验,最后一键把缺失的文件复制过去。

文件对比校验复制工具 第1张
工具功能说明
这个工具是用 Python 写的,做成了一个简单的桌面小程序,核心有三块功能:
- 生成文件路径清单
- 选中一个“新项目”文件夹,工具会递归扫描其中的所有文件。
- 自动生成一个
file_list.txt路径清单,保存在工具同目录。
- 存在性校验
- 先选择刚才生成的路径清单,再选择“旧项目”的根目录。
- 工具会以“锚点目录名”为基准(不区分大小写),从清单里提取相对路径,在旧项目里挨个查找。
- 最终会生成两个文件:
exist_files.txt:旧项目里已存在的文件路径missing_files.txt:旧项目里缺失的文件路径
文件对比校验复制工具 第2张
- 一键复制缺失文件
- 在第三个标签页中,选择路径清单、源根目录(新项目)、目标目录(旧项目)。
- 工具会按同样的“锚点 + 相对路径”的方式,把能找到的文件从源复制到目标,并保持原有目录结构。
- 同样会生成
copied_files.txt/not_copied_files.txt两个日志,方便回溯。
整个设计思路就是:只认锚点目录名,后半段路径全部按原样匹配复制,且不区分大小写,这样在 request / Request 这类目录名不一致的项目中,也能稳定工作。
使用步骤
- 下载/打包工具
- 你可以直接下载附件中的 exe 版本使用;
- 也可以把下面的 Python 源码自己用
pyinstaller之类重新打包成 exe。
- 在“新项目”生成路径清单
- 打开工具 → 切到“路径清单”标签;
- 选择新项目根目录,工具会自动扫描并生成
file_list.txt。
- 在“旧项目”做存在性校验
- 切到“存在性校验”;
- 先选择刚才生成的
file_list.txt; - 再选择旧项目中的锚点根目录(例如旧项目里的
request目录); - 点击“执行文件存在性校验”,稍等即可得到存在/缺失两个清单文件。
- 一键复制缺失文件(可选)
- 切到“复制文件”;
- 同样选择路径清单、源根目录(新项目)、目标目录(旧项目);
- 执行复制操作,缺失的文件会按原目录结构复制过去。
Python 源码(完整)
下面是工具的完整源码,可以直接保存为
main.py,然后自行打包或运行。代码未做任何修改,你可以直接对比。
import os
import sys
import shutil
from pathlib import Path
import tkinter as tk
from tkinter import filedialog, messagebox
from tkinter import ttk
class ProgressDialog:
"""简洁进度弹窗"""
def __init__(self, parent, title: str, total: int):
self.total = max(1, int(total))
self.top = tk.Toplevel(parent)
self.top.title(title)
self.top.transient(parent)
self.top.resizable(False, False)
self.top.configure(bg="#1a1a1a")
self.top.attributes('-topmost', True)
self.top.update_idletasks()
pw = 360
ph = 120
px = parent.winfo_rootx() + (parent.winfo_width() - pw) // 2
py = parent.winfo_rooty() + (parent.winfo_height() - ph) // 2
self.top.geometry(f"{pw}x{ph}+{max(0, px)}+{max(0, py)}")
wrap = tk.Frame(self.top, bg="#1f2937", highlightthickness=1, highlightbackground="#334155")
wrap.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
title_lbl = tk.Label(wrap, text=title, font=("Microsoft YaHei UI", 11, "bold"), bg="#1f2937", fg="#e5e7eb")
title_lbl.pack(anchor="w")
self.var_text = tk.StringVar(value="准备中…")
text_lbl = tk.Label(wrap, textvariable=self.var_text, font=("Microsoft YaHei UI", 9), bg="#1f2937",
fg="#9ca3af")
text_lbl.pack(anchor="w", pady=(6, 6))
self.pb = ttk.Progressbar(wrap, orient=tk.HORIZONTAL, mode='determinate', length=300, maximum=self.total)
self.pb.pack(fill=tk.X)
self.var_percent = tk.StringVar(value="0%")
percent_lbl = tk.Label(wrap, textvariable=self.var_percent, font=("Consolas", 10), bg="#1f2937", fg="#e5e7eb")
percent_lbl.pack(anchor="e", pady=(6, 0))
self.top.grab_set()
self.top.protocol("WM_DELETE_WINDOW", lambda: None)
def step(self, i: int, note=None):
i = max(0, min(self.total, int(i)))
if note:
self.var_text.set(note)
self.pb['value'] = i
pct = int(i * 100 / self.total)
self.var_percent.set(f"{pct}%")
self.top.update_idletasks()
def close(self):
try:
self.top.grab_release()
except Exception:
pass
self.top.destroy()
class FileToolApp:
"""
文件路径工具 - 锚点模式(不区分大小写)
功能2 & 3 均使用「锚点目录名」机制,并忽略大小写匹配
"""
def __init__(self, root):
self.root = root
self.root.title("文件路径工具—老吴搭建教程")
self.root.geometry("1000x760")
self.root.minsize(900, 640)
# 主题
self.theme = "dark"
self._init_theme_tokens()
self._apply_theme()
# 应用目录
self.app_dir = self._resolve_app_dir()
try:
self.app_dir.mkdir(parents=True, exist_ok=True)
except Exception:
pass
# 状态变量
self.list_file_for_check = None
self.root_dir_for_check = None
self.list_file_for_copy = None
self.source_root_for_copy = None
self.target_dir_for_copy = None
# UI 变量
self.var_list_check = tk.StringVar(value="未选择")
self.var_root_check = tk.StringVar(value="未选择")
self.var_list_copy = tk.StringVar(value="未选择")
self.var_source_copy = tk.StringVar(value="未选择")
self.var_target_copy = tk.StringVar(value="未选择")
self._build_ui()
def _init_theme_tokens(self):
self.TOKENS = {
"dark": {
"bg": "#0f1419",
"elev": "#151b24",
"card": "#1b2332",
"primary": "#3b82f6",
"primary_hover": "#2563eb",
"text": "#e5e7eb",
"muted": "#9aa4b2",
"border": "#263041",
"badge": "#222b3b",
},
"light": {
"bg": "#f6f7fb",
"elev": "#ffffff",
"card": "#ffffff",
"primary": "#2563eb",
"primary_hover": "#1d4ed8",
"text": "#111827",
"muted": "#6b7280",
"border": "#e5e7eb",
"badge": "#eef2ff",
},
}
def _resolve_app_dir(self):
try:
if getattr(sys, 'frozen', False):
return Path(sys.executable).resolve().parent
except Exception:
pass
return Path(__file__).resolve().parent
def _apply_theme(self):
t = self.TOKENS[self.theme]
self.bg = t["bg"]
self.elev = t["elev"]
self.card = t["card"]
self.primary = t["primary"]
self.primary_hover = t["primary_hover"]
self.text = t["text"]
self.muted = t["muted"]
self.border = t["border"]
self.badge_bg = t["badge"]
style = ttk.Style()
try:
style.theme_use('clam')
except Exception:
pass
style.configure('TFrame', background=self.bg)
style.configure('TLabel', background=self.bg, foreground=self.text)
style.configure('Thin.TSeparator', background=self.border)
style.configure('TNotebook', background=self.bg, borderwidth=0)
style.configure('TNotebook.Tab', padding=(18, 10), font=('Microsoft YaHei UI', 10), background=self.elev,
foreground=self.muted)
style.map('TNotebook.Tab',
background=[('selected', self.card), ('active', self.card)],
foreground=[('selected', self.text), ('active', self.text)])
style.configure('Primary.TButton',
background=self.primary,
foreground='#ffffff',
borderwidth=0,
focusthickness=0,
padding=(18, 12),
font=('Microsoft YaHei UI', 10, 'bold'))
style.map('Primary.TButton',
background=[('active', self.primary_hover), ('pressed', self.primary_hover)],
foreground=[('disabled', '#cccccc')])
style.configure('PrimaryHover.TButton',
background=self.primary_hover,
foreground='#ffffff',
borderwidth=0,
focusthickness=0,
padding=(18, 12),
font=('Microsoft YaHei UI', 10, 'bold'))
style.configure('Badge.TLabel', background=self.badge_bg, foreground=self.text, padding=(8, 3))
def _toggle_theme(self):
self.theme = 'light' if self.theme == 'dark' else 'dark'
self._apply_theme()
for w in self.root.winfo_children():
w.destroy()
self._build_ui()
def _build_ui(self):
self.root.configure(bg=self.bg)
self._build_header()
notebook_frame = tk.Frame(self.root, bg=self.bg)
notebook_frame.pack(fill=tk.BOTH, expand=True, padx=18, pady=(8, 12))
nb = ttk.Notebook(notebook_frame)
nb.pack(fill=tk.BOTH, expand=True)
tab1 = tk.Frame(nb, bg=self.bg)
tab2 = tk.Frame(nb, bg=self.bg)
tab3 = tk.Frame(nb, bg=self.bg)
nb.add(tab1, text="? 路径清单")
nb.add(tab2, text="✓ 存在性校验")
nb.add(tab3, text="? 复制文件")
self._tab_generate_list(tab1)
self._tab_check_exist(tab2)
self._tab_copy_files(tab3)
self._build_footer()
def _build_header(self):
header_wrap = tk.Frame(self.root, bg=self.bg)
header_wrap.pack(fill=tk.X, padx=18, pady=(18, 8))
header = tk.Frame(header_wrap, bg=self.elev, highlightthickness=1, highlightbackground=self.border)
header.pack(fill=tk.X)
top_bar = tk.Frame(header, bg=self.primary, height=3)
top_bar.pack(fill=tk.X)
content = tk.Frame(header, bg=self.elev)
content.pack(fill=tk.BOTH, expand=True, padx=16, pady=12)
left = tk.Frame(content, bg=self.elev)
left.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
title = tk.Label(left, text="文件路径工具", font=("Microsoft YaHei UI", 20, "bold"), bg=self.elev,
fg=self.primary)
title.pack(anchor="w")
subtitle = tk.Label(left, text="基于锚点目录名校验与复制 · 不区分大小写", font=("Microsoft YaHei UI", 10), bg=self.elev,
fg=self.muted)
subtitle.pack(anchor="w", pady=(6, 0))
right = tk.Frame(content, bg=self.elev)
right.pack(side=tk.RIGHT)
theme_btn = ttk.Button(
right,
text=("切换到亮色" if self.theme == 'dark' else "切换到暗色"),
command=self._toggle_theme,
style='Primary.TButton',
)
theme_btn.pack()
def _build_footer(self):
footer_wrap = tk.Frame(self.root, bg=self.bg)
footer_wrap.pack(fill=tk.X, padx=18, pady=(0, 18))
footer = tk.Frame(footer_wrap, bg=self.elev, highlightthickness=1, highlightbackground=self.border)
footer.pack(fill=tk.X)
content = tk.Frame(footer, bg=self.elev)
content.pack(fill=tk.BOTH, expand=True, padx=14, pady=10)
tip = tk.Label(content, text="提示:以所选目录名为锚点(不区分大小写),提取路径后半段进行匹配。", font=("Microsoft YaHei UI", 9), bg=self.elev,
fg=self.muted)
tip.pack(side=tk.LEFT)
brand = tk.Label(content, text="文件校验 匹配 复制", font=("Microsoft YaHei UI", 9), bg=self.elev, fg=self.muted)
brand.pack(side=tk.RIGHT)
def _card(self, parent, title_text: str, desc_text: str):
card = tk.Frame(parent, bg=self.elev, highlightthickness=1, highlightbackground=self.border)
card.pack(fill=tk.X, pady=12)
top = tk.Frame(card, bg=self.primary, height=3)
top.pack(fill=tk.X)
body = tk.Frame(card, bg=self.elev)
body.pack(fill=tk.BOTH, expand=True, padx=16, pady=14)
title = tk.Label(body, text=title_text, font=("Microsoft YaHei UI", 13, "bold"), bg=self.elev, fg=self.primary)
title.pack(anchor="w")
if desc_text:
desc = tk.Label(body, text=desc_text, font=("Microsoft YaHei UI", 9), bg=self.elev, fg=self.muted)
desc.pack(anchor="w", pady=(6, 6))
sep = tk.Frame(body, bg=self.border, height=1)
sep.pack(fill=tk.X, pady=8)
def on_enter(_): card.configure(highlightbackground=self.primary)
def on_leave(_): card.configure(highlightbackground=self.border)
card.bind("<Enter>", on_enter)
card.bind("<Leave>", on_leave)
return body
def _primary_btn(self, parent, text, command):
btn = ttk.Button(parent, text=text, command=command, style='Primary.TButton')
btn.pack(fill=tk.X)
btn.bind("<Enter>", lambda e: btn.configure(style='PrimaryHover.TButton'))
btn.bind("<Leave>", lambda e: btn.configure(style='Primary.TButton'))
return btn
def _line_item(self, parent, label_text: str, var: tk.StringVar):
row = tk.Frame(parent, bg=self.elev)
row.pack(fill=tk.X, pady=6)
label = tk.Label(row, text=label_text, font=("Microsoft YaHei UI", 10), bg=self.elev, fg=self.text)
label.pack(side=tk.LEFT)
value = ttk.Label(row, textvariable=var, style='Badge.TLabel')
value.pack(side=tk.RIGHT)
def _tab_generate_list(self, tab):
container = tk.Frame(tab, bg=self.bg)
container.pack(fill=tk.BOTH, expand=True, padx=6, pady=8)
card = self._card(container, "生成文件夹路径清单", "扫描文件夹中的所有文件并生成完整路径列表")
self._primary_btn(card, "选择文件夹并生成路径清单", self.generate_path_list)
def _tab_check_exist(self, tab):
container = tk.Frame(tab, bg=self.bg)
container.pack(fill=tk.BOTH, expand=True, padx=6, pady=8)
card = self._card(container, "校验文件是否存在", "以所选目录名为锚点(不区分大小写),从清单路径中提取子路径并在本地查找")
self._primary_btn(card, "选择路径清单文件", self.select_list_file_for_check)
self._primary_btn(card, "选择本地根目录(如 request / Request)", self.select_root_dir_for_check)
self._primary_btn(card, "执行文件存在性校验", self.check_files_existence)
self._line_item(card, "路径清单:", self.var_list_check)
self._line_item(card, "本地根目录:", self.var_root_check)
def _tab_copy_files(self, tab):
container = tk.Frame(tab, bg=self.bg)
container.pack(fill=tk.BOTH, expand=True, padx=6, pady=8)
card = self._card(container, "复制匹配的文件", "基于锚点(不区分大小写)提取子路径,从源目录复制到目标目录")
self._primary_btn(card, "选择路径清单文件", self.select_list_file_for_copy)
self._primary_btn(card, "选择源根目录(如 request / Request)", self.select_source_root_for_copy)
self._primary_btn(card, "选择目标文件夹", self.select_target_dir_for_copy)
self._primary_btn(card, "执行文件复制", self.copy_matched_files)
self._line_item(card, "路径清单:", self.var_list_copy)
self._line_item(card, "源根目录:", self.var_source_copy)
self._line_item(card, "目标文件夹:", self.var_target_copy)
# ===== 功能实现 =====
def generate_path_list(self):
folder = filedialog.askdirectory(title="选择要扫描的文件夹")
if not folder:
return
folder_path = Path(folder)
output_file = self.app_dir / "file_list.txt"
paths = []
for file in folder_path.rglob("*"):
if file.is_file():
paths.append(str(file.resolve()))
with open(output_file, "w", encoding="utf-8") as f:
f.write("\n".join(paths))
messagebox.showinfo("完成", f"已生成路径清单到:\n{output_file}\n\n共找到 {len(paths)} 个文件")
def select_list_file_for_check(self):
file = filedialog.askopenfilename(title="选择路径清单文本文件",
filetypes=[("Text files", "*.txt"), ("All files", "*.*")])
if file:
self.list_file_for_check = Path(file)
self.var_list_check.set(self.list_file_for_check.name)
messagebox.showinfo("已选择", f"路径清单:\n{self.list_file_for_check}")
def select_root_dir_for_check(self):
folder = filedialog.askdirectory(title="选择本地根目录(如 request / Request)")
if folder:
self.root_dir_for_check = Path(folder)
self.var_root_check.set(self.root_dir_for_check.name)
messagebox.showinfo("已选择", f"本地根目录:\n{self.root_dir_for_check}")
def check_files_existence(self):
if not self.list_file_for_check or not self.root_dir_for_check:
messagebox.showerror("错误", "请先选择路径清单和本地根目录!")
return
anchor_name = self.root_dir_for_check.name
anchor_lower = anchor_name.lower() # ← 关键:统一转小写用于匹配
exist_list = []
missing_list = []
with open(self.list_file_for_check, "r", encoding="utf-8") as f:
lines = [line.strip() for line in f if line.strip()]
pd = ProgressDialog(self.root, "正在校验文件…", len(lines))
try:
for i, full_path in enumerate(lines, start=1):
try:
normalized = full_path.replace("\\", "/")
parts = normalized.split("/")
# 从后往前找锚点(忽略大小写)
idx = -1
for j in range(len(parts) - 1, -1, -1):
if parts[j].lower() == anchor_lower:
idx = j
break
if idx == -1:
missing_list.append(full_path)
continue
rel_parts = parts[idx + 1:]
if not rel_parts:
missing_list.append(full_path)
continue
local_file = self.root_dir_for_check / Path(*rel_parts)
if local_file.exists():
exist_list.append(full_path)
else:
missing_list.append(full_path)
except Exception:
missing_list.append(full_path)
pd.step(i, note=f"处理 {i}/{len(lines)}…")
finally:
pd.close()
exist_file = self.app_dir / "exist_files.txt"
missing_file = self.app_dir / "missing_files.txt"
with open(exist_file, "w", encoding="utf-8") as f:
f.write("\n".join(exist_list))
with open(missing_file, "w", encoding="utf-8") as f:
f.write("\n".join(missing_list))
messagebox.showinfo(
"完成",
f"校验完成!\n\n存在: {len(exist_list)} 个\n缺失: {len(missing_list)} 个\n\n结果已保存到程序目录。"
)
def select_list_file_for_copy(self):
file = filedialog.askopenfilename(title="选择路径清单文本文件",
filetypes=[("Text files", "*.txt"), ("All files", "*.*")])
if file:
self.list_file_for_copy = Path(file)
self.var_list_copy.set(self.list_file_for_copy.name)
messagebox.showinfo("已选择", f"路径清单:\n{self.list_file_for_copy}")
def select_source_root_for_copy(self):
folder = filedialog.askdirectory(title="选择源根目录(如 request / Request)")
if folder:
self.source_root_for_copy = Path(folder)
self.var_source_copy.set(self.source_root_for_copy.name)
messagebox.showinfo("已选择", f"源根目录:\n{self.source_root_for_copy}")
def select_target_dir_for_copy(self):
folder = filedialog.askdirectory(title="选择目标文件夹(复制到此处)")
if folder:
self.target_dir_for_copy = Path(folder)
self.var_target_copy.set(self.target_dir_for_copy.name)
messagebox.showinfo("已选择", f"目标文件夹:\n{self.target_dir_for_copy}")
def copy_matched_files(self):
if not all([self.list_file_for_copy, self.source_root_for_copy, self.target_dir_for_copy]):
messagebox.showerror("错误", "请先选择路径清单、源根目录和目标文件夹!")
return
anchor_name = self.source_root_for_copy.name
anchor_lower = anchor_name.lower()
copied_list = []
not_copied_list = []
with open(self.list_file_for_copy, "r", encoding="utf-8") as f:
lines = [line.strip() for line in f if line.strip()]
pd = ProgressDialog(self.root, "正在复制匹配的文件…", len(lines))
try:
for i, full_path in enumerate(lines, start=1):
try:
normalized = full_path.replace("\\", "/")
parts = normalized.split("/")
# 从后往前找锚点(忽略大小写)
idx = -1
for j in range(len(parts) - 1, -1, -1):
if parts[j].lower() == anchor_lower:
idx = j
break
if idx == -1:
not_copied_list.append(full_path)
continue
rel_parts = parts[idx + 1:]
if not rel_parts:
not_copied_list.append(full_path)
continue
rel_path = Path(*rel_parts)
src_file = self.source_root_for_copy / rel_path
dst_file = self.target_dir_for_copy / rel_path # 保留相对结构
if src_file.exists() and src_file.is_file():
dst_file.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(src_file, dst_file)
copied_list.append(full_path)
else:
not_copied_list.append(full_path)
except Exception:
not_copied_list.append(full_path)
pd.step(i, note=f"处理 {i}/{len(lines)}…")
finally:
pd.close()
# 保存日志
copied_log = self.app_dir / "copied_files.txt"
not_copied_log = self.app_dir / "not_copied_files.txt"
with open(copied_log, "w", encoding="utf-8") as f:
f.write("\n".join(copied_list))
with open(not_copied_log, "w", encoding="utf-8") as f:
f.write("\n".join(not_copied_list))
messagebox.showinfo(
"完成",
f"复制完成!\n\n成功: {len(copied_list)} 个\n未找到或失败: {len(not_copied_list)} 个\n\n"
f"结果已保存到程序目录:\n- copied_files.txt\n- not_copied_files.txt"
)
if __name__ == "__main__":
root = tk.Tk()
try:
root.tk.call('tk', 'scaling', 1.15)
except Exception:
pass
app = FileToolApp(root)
root.mainloop()
其它类似工具
- 文件对比软件 UltraCompare for mac v23.0.0.30 中文修复版
- 文件对比工具 ExamDiff Pro Master Edition v10.0.1.12 免激活码
- 文件对比软件 Beyond Compare v4.4.6 免激活
工具参数
- 工具名称:文件对比校验复制工具
- 实现语言:Python + Tkinter
- 工具格式:exe(附 Python 源码,可自行打包)
- 工具大小:9.6M
下载地址:
隐藏内容,解锁需要先评论本文
评论后刷新解锁











![[源码分享] 创胜系列定制版本嘉年华房卡源代码【开发引擎Cocos Creator2.4.3】-](https://www.264rose.com/wp-content/uploads/2024/10/c4ca4238a0b9238-10.jpg)




