ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

对比两个文件夹中的重复文件名:Python、PowerShell、bat 三种方案

对比两个文件夹中的重复文件名:Python、PowerShell、bat 三种方案 我们这次来看一个非常实用的小问题如何对比两个文件夹中的文件找出文件名重复的部分。这类需求在工作里出现的频率其实很高下载素材、整理照片、迁移旧硬盘、合并两台电脑的资料库时经常遇到两个目录里混着同名但来源不同的文件。几百个文件还能靠肉眼扫一遍几千上万的时候就完全不行了。这篇文章先给你结论只对比文件名时最快最稳的方案是 Python 脚本想零安装PowerShell 一步也能做临时用一次批处理 bat 也够用。核心原理都是“遍历目录 - 收集文件名 - 求交集”完全不读文件内容所以速度非常快十万级文件量也能在几秒钟内跑完。文章不会只给你一个脚本就完事而是把三种方案都写清楚给出可复制的代码、测试方法、输出报告格式以及大规模文件夹下如何避免踩坑。涉及同名但内容不同的情况也会告诉你怎么判断、怎么处理。1. 核心能力速览能力项说明核心功能对比两个文件夹中的文件名找出重复项对比粒度仅文件名不读取文件内容可选增强同名时对比大小、修改时间、文件哈希支持平台Windows / Linux / macOS方案一Python 3.8推荐大批量、需二次处理场景方案二Windows PowerShell零安装适合快速验证方案三批处理 bat最轻量适合临时任务递归子目录可配置Python 和 PowerShell 默认支持递归输出结果控制台打印 / CSV 报告 / 文本文件是否自动删除文件不删除默认仅输出重复清单适合场景资料整理、备份合并、迁移前检查、素材库去重不适合场景需要判断文件内容是否完全一致时仅看文件名不够核心思路就一句话把 A 文件夹里所有文件的名字收集成一个集合把 B 文件夹里所有文件的名字收集成另一个集合然后求交集。集合运算的时间复杂度接近线性文件再多也能撑住。2. 适用场景与使用边界2.1 适合解决的典型问题迁移数据前想确认两个目录里有没有同名文件避免覆盖。整理 NAS 或移动硬盘时找出散落在不同子目录里的重复素材。同步盘下载了多份同名压缩包想快速定位。测试环境要合并两份代码或资源目录先查重。批量整理照片、视频、文档时按文件名做初步去重。这类问题的共同点你只需要知道“有没有同名文件、同名文件分别在哪个路径”不需要立刻判断文件内容是否一致。这时候只对比文件名是最优解因为不需要读文件体磁盘 IO 压力小速度也快得多。2.2 不适合什么场景只对比文件名有一个致命盲区同名不代表同内容。比如两个目录都有一份report.docx可能一个是终稿、一个是旧版都叫IMG_001.JPG可能完全是两张照片。如果你要做的是“内容级去重”必须引入文件大小、修改时间或者更严格的文件哈希MD5 / SHA-256校验。另外如果两个文件夹里文件数量差异极大比如 A 有 10 万个文件、B 只有 100 个建议反过来遍历小目录、在大的集合里查这样内存占用更稳定。2.3 使用边界与安全提醒本工具默认只输出重复清单不删除任何文件。删除前必须人工确认。处理他人数据、公司内部数据时注意文件隐私和版权边界不要随意对外分享对比结果。如果后续要用脚本自动删除或移动同名文件务必先备份并且确认两个同名文件是否真的可以覆盖。3. 环境准备与前置条件三种方案对环境要求不一样下面分别说明。3.1 Python 方案操作系统Windows / Linux / macOS 均可Python 版本建议 3.8不需要安装第三方库只用标准库os、sys、pathlib、csv命令行运行即可3.2 PowerShell 方案操作系统Windows 7 以上版本PowerShell 5.1 或更高Windows 10/11 自带不需要额外安装任何东西3.3 批处理 bat 方案操作系统Windows直接双击或命令行执行有中文路径时需要注意代码页设置后面会专门说3.4 建议的测试目录结构不管是哪个方案建议先建一个小的测试目录验证逻辑test_dir/ ├── folderA/ │ ├── a.txt │ ├── b.txt │ └── sub/ │ └── c.txt └── folderB/ ├── b.txt ├── c.txt └── d.txt预期结果b.txt是根目录下同名重复c.txt是 A 的子目录和 B 的根目录之间同名重复。4. 方案一Python 脚本推荐最灵活Python 方案是我最推荐的主力方案。原因有三个跨平台Windows、Linux、macOS 一套代码通用。只对比文件名时set交集运算非常快。很容易扩展比如输出 CSV、加上大小校验、按目录分组。4.1 基础版仅对比文件名并输出重复清单# compare_filenames.py import os import sys from pathlib import Path def collect_filenames(directory: Path, recursive: bool True) - set: 收集目录下所有文件的文件名返回集合。 只取 name不包含路径因此不同子目录里的同名文件会被视为重复。 names set() if recursive: # 递归遍历所有子目录 for root, dirs, files in os.walk(directory): for file_name in files: names.add(file_name) else: # 只遍历当前目录一层 for entry in os.scandir(directory): if entry.is_file(): names.add(entry.name) return names def main(): if len(sys.argv) 3: print(用法: python compare_filenames.py 目录A 目录B [--no-recursive]) sys.exit(1) dir_a Path(sys.argv[1]) dir_b Path(sys.argv[2]) recursive --no-recursive not in sys.argv if not dir_a.exists() or not dir_b.exists(): print(错误: 目录不存在) sys.exit(1) print(f正在扫描目录 A: {dir_a} (recursive{recursive})) names_a collect_filenames(dir_a, recursive) print(f正在扫描目录 B: {dir_b} (recursive{recursive})) names_b collect_filenames(dir_b, recursive) duplicates sorted(names_a names_b) print(f\n目录 A 文件数: {len(names_a)}) print(f目录 B 文件数: {len(names_b)}) print(f重复文件名数量: {len(duplicates)}\n) if not duplicates: print(没有找到重复文件名) return for i, name in enumerate(duplicates, start1): print(f{i}. {name}) if __name__ __main__: main()运行方式python compare_filenames.py D:/backup/music D:/backup/music_new默认递归子目录。如果不希望递归python compare_filenames.py D:/backup/music D:/backup/music_new --no-recursive这个脚本的基础逻辑很容易理解分别调用collect_filenames收集两个目录的文件名集合然后names_a names_b直接取交集。4.2 升级版输出 CSV 报告标注每个重复文件的具体路径实际使用中光知道文件名重复还不够你需要知道“这个文件在 A 里到底在哪、在 B 里到底在哪”。升级版脚本能把具体路径也输出到 CSV 里方便用 Excel 打开后人工审阅。# compare_filenames_csv.py import csv import os import sys from pathlib import Path from collections import defaultdict def collect_file_paths(directory: Path, recursive: bool True) - dict: 返回字典: {文件名: [完整路径, ...]} 同一个文件名可能出现多次因为不同子目录可能都有同名文件。 file_map defaultdict(list) if recursive: for root, dirs, files in os.walk(directory): for file_name in files: full_path os.path.join(root, file_name) file_map[file_name].append(full_path) else: for entry in os.scandir(directory): if entry.is_file(): file_map[entry.name].append(entry.path) return file_map def main(): if len(sys.argv) 3: print(用法: python compare_filenames_csv.py 目录A 目录B [--no-recursive]) sys.exit(1) dir_a Path(sys.argv[1]) dir_b Path(sys.argv[2]) recursive --no-recursive not in sys.argv map_a collect_file_paths(dir_a, recursive) map_b collect_file_paths(dir_b, recursive) duplicate_names sorted(set(map_a.keys()) set(map_b.keys())) output_file duplicate_report.csv with open(output_file, w, newline, encodingutf-8-sig) as f: writer csv.writer(f) writer.writerow([文件名, 目录A路径, 目录B路径]) for name in duplicate_names: paths_a map_a[name] paths_b map_b[name] writer.writerow([name, ; .join(paths_a), ; .join(paths_b)]) print(f完成共发现 {len(duplicate_names)} 个重复文件名。) print(f报告已输出到: {output_file}) if __name__ __main__: main()注意encodingutf-8-sig这个细节很重要写字表用 UTF-8 带 BOM 的格式Excel 才不会把中文显示成乱码。4.3 扩展思路同名但大小不同的文件单独标记很多场景下同名文件如果大小也一致基本可以判断是同一份文件如果大小不同极有可能是内容不同的版本。可以在 CSV 输出里把文件大小也带上。def collect_file_details(directory: Path) - dict: 返回 {文件名: [(完整路径, 文件大小), ...]} file_map defaultdict(list) for root, dirs, files in os.walk(directory): for file_name in files: full_path os.path.join(root, file_name) size os.path.getsize(full_path) file_map[file_name].append((full_path, size)) return file_map这样处理后CSV 里可以加一列“大小是否一致”人工决策时更直观。5. 方案二PowerShell 命令Windows 零安装如果你在 Windows 上不想安装 Python只想快速知道两个文件夹里有哪些同名文件PowerShell 是最快的路径。5.1 一条命令直接查重复文件名$A Get-ChildItem D:\backup\music -Recurse -File | Select-Object -ExpandProperty Name $B Get-ChildItem D:\backup\music_new -Recurse -File | Select-Object -ExpandProperty Name $A | Where-Object { $_ -in $B } | Sort-Object -Unique拆开解释一下Get-ChildItem获取目录下所有文件-Recurse表示递归子目录-File只取文件、跳过目录。Select-Object -ExpandProperty Name把输出精简成文件名列表。Where-Object { $_ -in $B }过滤出同时在 B 里出现的文件名。Sort-Object -Unique排序并去重。如果是 PowerShell 3.0 以上-in操作符是可用的Windows 10/11 自带的 PowerShell 5.1 完全没问题。5.2 详细版输出每个重复文件的完整路径上面那条命令只输出文件名不告诉你文件在哪。想要完整路径换成$dirA D:\backup\music $dirB D:\backup\music_new $filesA Get-ChildItem $dirA -Recurse -File $filesB Get-ChildItem $dirB -Recurse -File $result $filesA | Where-Object { $_.Name -in $filesB.Name } | Select-Object { Name 文件名; Expression { $_.Name } }, { Name 目录A路径; Expression { $_.FullName } } $result | Format-Table -AutoSize这段命令的本质是先拿 A 里的每个文件判断它的文件名是否在 B 里出现过然后输出文件名和 A 中的完整路径。如果你想看 B 中的路径再单独展开$filesB即可。5.3 导出 CSV 报告$dirA D:\backup\music $dirB D:\backup\music_new $filesA Get-ChildItem $dirA -Recurse -File $filesB Get-ChildItem $dirB -Recurse -File $duplicates foreach ($file in $filesA) { $matchInB $filesB | Where-Object { $_.Name -eq $file.Name } | Select-Object -First 1 if ($matchInB) { [PSCustomObject]{ 文件名 $file.Name 目录A路径 $file.FullName 目录B路径 $matchInB.FullName } } } $duplicates | Export-Csv -Path duplicate_report.csv -NoTypeInformation -Encoding UTF8 Write-Host 共发现 $($duplicates.Count) 个重复文件名这里用Select-Object -First 1限制了 B 中如果多个子目录都有同名文件只取第一个路径。如果你想把所有路径都列出来需要再套一层循环或先Group-Object分组。6. 方案三批处理 bat 脚本最轻量bat 脚本最大的优势是双击就能跑不需要任何运行环境。缺点是中文编码处理比较麻烦复杂逻辑写起来很别扭。适合一次性的临时查重。6.1 基础版 bat 脚本echo off chcp 65001 nul setlocal enabledelayedexpansion set DIR_AC:\test\folderA set DIR_BC:\test\folderB set OUTPUTduplicate_names.txt if exist %OUTPUT% del %OUTPUT% for /r %DIR_A% %%F in (*) do ( set shotName%%~nxF if exist %DIR_B%\!shotName! ( echo !shotName! %OUTPUT% ) ) echo 对比完成重复文件名已写入 %OUTPUT% pause这个脚本的逻辑很朴素遍历 A 里的每个文件取文件名拼成一个DIR_B\文件名的路径然后用if exist判断 B 里有没有这个名字的文件。有就输出到文本。注意几点chcp 65001把代码页切换到 UTF-8能缓解一部分中文路径乱码问题但 Windows 自带记事本查看 bat 文件时要另存为 UTF-8 编码否则还是可能乱。%%~nxF用来取文件名和扩展名不带完整路径。!shotName!使用了enabledelayedexpansion因为在for循环内改变量值必须用!而不是%。6.2 带完整路径输出的 bat 版本如果想把重复文件的源路径也输出到报告里echo off chcp 65001 nul setlocal enabledelayedexpansion set DIR_AC:\test\folderA set DIR_BC:\test\folderB set OUTPUTduplicate_full_report.txt if exist %OUTPUT% del %OUTPUT% for /r %DIR_A% %%F in (*) do ( set shotName%%~nxF if exist %DIR_B%\!shotName! ( echo [重复] !shotName! %OUTPUT% echo 目录A: %%F %OUTPUT% echo 目录B: %DIR_B%\!shotName! %OUTPUT% echo. %OUTPUT% ) ) echo 对比完成报告已写入 %OUTPUT% pausebat 方案有两个天然缺陷%DIR_B%\!shotName!这种方式只适用于文件名直接平铺在 B 根目录的情况。如果同名文件在 B 的更深层子目录里if exist会判断失败漏掉结果。只对比文件名、不关心文件内容遇到同名但扩展名不同的大写小写差异Windows 默认不区分大小写if exist会认为是同一个文件。所以我的建议是bat 方案只适合知道两个目录结构都比较简单、文件都在根目录或浅层子目录的临时场景正式批量处理优先用 Python 或 PowerShell。7. 功能测试与效果验证无论用哪个方案第一次跑之前建议先按下面的步骤做一遍功能验证。7.1 构造测试目录以 Python 方案为例先建一个测试目录test_dir/ ├── folderA/ │ ├── a.txt │ ├── b.txt │ └── sub/ │ └── c.txt └── folderB/ ├── b.txt ├── c.txt └── d.txt预期结果b.txt在 A 根目录和 B 根目录都存在重复。c.txt在 A 的 sub 子目录和 B 的根目录都存在递归模式下也应该被发现。a.txt只在 A 有d.txt只在 B 有不是重复。7.2 执行测试python compare_filenames_csv.py test_dir/folderA test_dir/folderB查看输出的duplicate_report.csv文件名目录A路径目录B路径b.txttest_dir/folderA/b.txttest_dir/folderB/b.txtc.txttest_dir/folderA/sub/c.txttest_dir/folderB/c.txt只要 CSV 里出现这两行就说明递归扫描、文件名提取、交集查找、CSV 输出这一整条链路都正常。7.3 测试用例覆盖实际使用前建议覆盖这几种典型输入测试用例目的两个空目录确认脚本不报错输出“未找到重复”只有一方有文件确认交集为空时逻辑正常中文文件名确认编码处理正确文件名相同但扩展名不同确认a.jpg和a.png不会误判为重复子目录嵌套三层以上确认递归深度没问题带空格和特殊字符的文件名确认路径拼接没 bug7.4 判断成功的标准输出的重复清单和你人工检查的结果完全一致。同名但不同内容、不同大小、不同修改时间的文件都能准确列出。CSV 用 Excel 打开时中文显示正常。大目录跑完后内存和磁盘占用稳定没有中途崩溃。8. 内容一致性校验与扩展功能只对比文件名能解决“有没有同名”但解决不了“同名文件是不是真的相同”。如果你的目标是把重复文件腾出来、合并目录建议在文件名对比的基础上增加内容校验。8.1 先比大小再比哈希不要一上来就全量计算 MD5那样会读取大量文件内容速度慢很多。推荐两层过滤先按文件名找重复。重复的文件里先比大小大小相同的再比 MD5。8.2 Python 哈希校验示例import hashlib def get_md5(file_path: str, chunk_size: int 8192) - str: 计算文件 MD5适合大数据量分块读取 hash_md5 hashlib.md5() with open(file_path, rb) as f: for chunk in iter(lambda: f.read(chunk_size), b): hash_md5.update(chunk) return hash_md5.hexdigest()使用方式for name in duplicate_names: for path_a in map_a[name]: for path_b in map_b[name]: size_a os.path.getsize(path_a) size_b os.path.getsize(path_b) if size_a ! size_b: print(f[大小不同] {name}: {size_a} vs {size_b}) else: md5_a get_md5(path_a) md5_b get_md5(path_b) if md5_a md5_b: print(f[内容相同] {name}) else: print(f[内容不同] {name})这个方案的优点是不会误删代价是速度会明显变慢。如果只是整理几千个文件不存在性能问题如果是对几十万文件做全量哈希要评估时间成本。8.3 按内容相同自动分组更完整的流程是把“同名且同哈希”的文件聚成一组打印出所有物理路径方便后续手动决定保留哪一份。from collections import defaultdict content_groups defaultdict(list) for name in duplicate_names: for path in map_a[name] map_b[name]: file_size os.path.getsize(path) file_hash get_md5(path) # key 同时也把大小算进去避免哈希碰撞和彻底重读 key (file_size, file_hash) content_groups[key].append((name, path))输出时凡是len(group) 2的就是内容重复的文件。9. 资源占用与大规模文件夹性能观察只对比文件名时资源占用主要集中在文件名集合的内存占用和目录遍历的磁盘 IO上 不是 CPU。9.1 内存占用Python 的set每个元素大约占几十字节。十万个文件名大约也就几十 MB 内存现代电脑完全无压力。真正的内存风险是把“完整路径列表”也全量存下来比如 CSV 升级版脚本中file_map存的都是完整路径字符串文件越多、路径越长内存会线性上涨。如果文件夹超过百万级文件建议改成“流式处理”先遍历小目录生成一个集合再遍历大目录每遇到一个文件名就用in判断命中后立即打印或写入文件不把结果全留在内存里。9.2 磁盘 IOos.walk或Get-ChildItem -Recurse只需要读取目录元数据不需要打开文件内容所以磁盘压力主要是目录项枚举机械硬盘也能轻松跑。真正费时间的阶段是哈希校验那才会大量读取文件内容。9.3 观察方法Windows 下打开“任务管理器 - 性能”观察内存和磁盘占用。Linux 下用free -h和iotop观察内存和 IO。跑大目录前先看磁盘剩余空间尽量把报告和临时文件放到另一个盘。9.4 降低资源占用的建议第一次跑只做文件名对比先不计算哈希。使用os.scandir代替os.listdir避免一次性创建大列表。报告用 CSV 增量写入不要最后一次性拼接巨大字符串。避免把整个目录树转成对象列表能用生成器就用生成器。10. 常见问题与排查方法问题现象可能原因排查方式解决方案中文文件名乱码bat 文件编码不对用记事本另存为 UTF-8加chcp 65001或改用 Python/PowerShell明明有同名文件但没检测出来没开递归或文件在深层子目录检查脚本递归参数加上--no-recursive去掉PowerShell 加-Recurseif exist判断不了深层目录bat 只拼接了根目录路径检查 B 目录文件层级换 Python os.walk同名但大小写不同Windows 不区分大小写看系统设置确认需求必要时用 Linux 或统一转小写再比同名但大小不同文件是不同版本在 CSV 里加“文件大小”列不要盲目删除先人工确认报告打开中文乱码CSV 编码问题用记事本或 Notepad 查看编码Python 写 CSV 用utf-8-sig大目录跑得很慢可能是计算了哈希看是等待磁盘还是 CPU 高先只做文件名对比不要全量哈希PowerShell 命令提示-in无法识别PowerShell 版本过低查$PSVersionTable.PSVersion改为Where-Object { $B -contains $_ }Python 脚本提示目录不存在路径写错或带引号格式问题打印sys.argv检查路径Windows 下用正斜杠或双反斜杠端口或临时文件占用导致脚本报错文件被其他程序占用检查文件句柄关闭相关程序后重试11. 最佳实践与工程化建议11.1 第一次先跑小目录不要一上来就对整个磁盘做对比。找个 100 个文件的小目录把脚本逻辑验证清楚再扩大范围。这样能避免脚本本身有 bug 而导致误判或漏判。11.2 输出报告而不是直接删除任何自动去重脚本第一步都应该是“生成报告”而不是“直接执行删除或移动”。报告生成后人工看一眼再决定怎么处理。批量删除前最稳妥的做法是把重复文件移动到一个_duplicates目录而不是直接删除观察一段时间后再清理。11.3 目录和文件命名规范如果你的工作流里经常要做目录对比建议平时就养成良好的命名习惯避免批量文件叫新建文档(1).docx、1111.jpg这种无意义名字。统一命名格式后文件名对比的误判率会大幅下降。11.4 脚本和报告分目录管理建议用这样的目录结构tools/ ├── scripts/ # 保存对比脚本 ├── reports/ # 输出对比报告 └── duplicates/ # 临时存放重复文件脚本只负责扫描和输出人工负责决策这样最安全。11.5 加入日志输出即使是本机小工具也建议在脚本里加一行运行时间统计。很多对比任务不是跑一次而是要反复跑有日志才能知道每次运行花了多久、有没有异常中断。import time start_time time.time() # 中间是处理逻辑 print(f耗时: {time.time() - start_time:.2f} 秒)12. 总结与下一步对比两个文件夹中的重复文件名本质上就是一个“目录遍历 文件名集合取交集”的问题。Python、PowerShell、bat 三种方案都能做实际场景里我更推荐 Python原因很直接跨平台、好扩展、能输出结构化报告后续要加大小校验、哈希校验、自动移动重复文件都很方便。先验证脚本逻辑再看重复报告最后做决策。删除或移动文件之前一定要确认两个同名文件是不是真的可以覆盖。只比文件名只能告诉你“名字撞了”想确定内容是否一样需要再加一步大小或哈希比对。如果你只需要做一次临时检查PowerShell 那条命令就够用如果你要管理的是几千上万个文件的长期整理工作建议直接花几分钟把 Python 脚本保存好以后随时复用。
返回列表