分享一个实用脚本:查找目录中重复的文件(python)

为了查询目录(含递归的子目录)中重复的文件,我写了一个 Python 脚本。

脚本代码(python):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
文件夹文件名比较工具
比较两个文件夹中的文件名,找出相同和不同的文件
"""

import os
import sys
from pathlib import Path
import re


def normalize_path(path_input):
"""
标准化用户输入的路径,处理各种格式

Args:
path_input (str): 用户输入的路径

Returns:
Path: 标准化后的路径对象
"""
if not path_input:
return None

# 去除首尾空白字符
path_str = path_input.strip()

# 去除首尾的引号(单引号或双引号)
if len(path_str) >= 2:
if (path_str.startswith('"') and path_str.endswith('"')) or \
(path_str.startswith("'") and path_str.endswith("'")):
path_str = path_str[1:-1]

# 再次去除空白字符(防止引号内有空格)
path_str = path_str.strip()

# 处理路径分隔符
# 在Windows上,将正斜杠转换为反斜杠
if os.name == 'nt': # Windows
path_str = path_str.replace('/', '\\')

# 创建Path对象,它会自动处理路径分隔符
try:
normalized_path = Path(path_str).resolve()
return normalized_path
except (OSError, ValueError) as e:
raise ValueError(f"无效的路径格式: {path_input} - {e}")


def _get_filenames_recursive(folder_path, filenames, max_depth, current_depth):
"""
递归获取文件名的辅助函数

Args:
folder_path (Path): 当前文件夹路径
filenames (set): 文件名集合
max_depth (int or None): 最大递归深度
current_depth (int): 当前深度
"""
try:
for item in folder_path.iterdir():
if item.is_file():
filenames.add(item.name)
elif item.is_dir():
# 检查是否还能继续递归
if max_depth is None or current_depth < max_depth:
_get_filenames_recursive(item, filenames, max_depth, current_depth + 1)
except PermissionError:
# 忽略权限错误,继续处理其他文件夹
pass
except Exception:
# 忽略其他错误,继续处理
pass


def get_filenames(folder_path, recursive=True, max_depth=None):
"""
获取文件夹中的所有文件名

Args:
folder_path (str or Path): 文件夹路径
recursive (bool): 是否递归遍历子文件夹
max_depth (int, optional): 最大递归深度,None表示无限制

Returns:
set: 包含所有文件名的集合

Raises:
FileNotFoundError: 文件夹不存在
NotADirectoryError: 路径不是文件夹
"""
# 如果是字符串,先标准化
if isinstance(folder_path, str):
folder_path = normalize_path(folder_path)

if not folder_path.exists():
raise FileNotFoundError(f"文件夹不存在: {folder_path}")
if not folder_path.is_dir():
raise NotADirectoryError(f"路径不是文件夹: {folder_path}")

filenames = set()

try:
if not recursive:
# 只遍历当前文件夹
for item in folder_path.iterdir():
if item.is_file():
filenames.add(item.name)
else:
# 递归遍历,考虑深度限制
_get_filenames_recursive(folder_path, filenames, max_depth, 0)

except PermissionError as e:
print(f"警告: 没有权限访问某些文件夹在 {folder_path}: {e}")
except Exception as e:
print(f"警告: 遍历文件夹时出现错误 {folder_path}: {e}")

return filenames


def compare_folders(folder1, folder2, show_details=False, recursive=True, max_depth=None):
"""
比对两个文件夹中的文件名

Args:
folder1 (str): 第一个文件夹路径
folder2 (str): 第二个文件夹路径
show_details (bool): 是否显示详细信息(仅在某个文件夹中的文件)
recursive (bool): 是否递归遍历子文件夹
max_depth (int, optional): 最大递归深度,None表示无限制

Returns:
tuple: (common_files, only_in_folder1, only_in_folder2) 如果 show_details=True
set: common_files 如果 show_details=False
"""
folder1_files = get_filenames(folder1, recursive, max_depth)
folder2_files = get_filenames(folder2, recursive, max_depth)

common_files = folder1_files.intersection(folder2_files)

if show_details:
only_in_folder1 = folder1_files - folder2_files
only_in_folder2 = folder2_files - folder1_files
return common_files, only_in_folder1, only_in_folder2

return common_files


def print_results(common_files, only_in_folder1=None, only_in_folder2=None,
folder1_path=None, folder2_path=None, recursive=True, max_depth=None):
"""
打印比较结果

Args:
common_files (set): 相同的文件名
only_in_folder1 (set, optional): 仅在文件夹1中的文件名
only_in_folder2 (set, optional): 仅在文件夹2中的文件名
folder1_path (str, optional): 文件夹1路径
folder2_path (str, optional): 文件夹2路径
recursive (bool): 是否递归遍历
max_depth (int, optional): 最大递归深度
"""
print("=" * 60)
print("文件夹比较结果")
print("=" * 60)

if folder1_path and folder2_path:
print(f"文件夹1: {folder1_path}")
print(f"文件夹2: {folder2_path}")

# 显示扫描配置
scan_info = "扫描配置: "
if not recursive:
scan_info += "仅当前文件夹"
else:
if max_depth is None:
scan_info += "递归所有子文件夹"
else:
scan_info += f"递归深度 {max_depth} 层"
print(scan_info)
print("-" * 60)

# 显示相同文件
if common_files:
print(f"相同的文件名 ({len(common_files)} 个):")
for file in sorted(common_files):
print(f" ✓ {file}")
else:
print("没有相同的文件名")

# 显示详细信息
if only_in_folder1 is not None:
print(f"\n仅在文件夹1中的文件 ({len(only_in_folder1)} 个):")
if only_in_folder1:
for file in sorted(only_in_folder1):
print(f" → {file}")
else:
print(" 无")

if only_in_folder2 is not None:
print(f"\n仅在文件夹2中的文件 ({len(only_in_folder2)} 个):")
if only_in_folder2:
for file in sorted(only_in_folder2):
print(f" → {file}")
else:
print(" 无")

print("=" * 60)


def display_path_examples():
"""显示路径输入示例"""
print("\n路径输入示例:")
print("Windows:")
print(" C:\\Users\\username\\Desktop")
print(" \"C:\\Users\\username\\Desktop\"")
print(" C:/Users/username/Desktop")
print("\nLinux/Mac:")
print(" /home/username/Desktop")
print(" \"/home/username/Desktop\"")
print(" ~/Desktop")
print("\n支持带引号和不带引号的路径,支持正斜杠和反斜杠\n")


def display_recursion_examples():
"""显示递归选项的说明"""
print("\n递归选项说明:")
print("• 仅当前文件夹: 只比较指定文件夹内的直接文件")
print("• 递归所有子文件夹: 比较所有子文件夹中的文件")
print("• 递归指定深度: 限制递归的层数")
print("\n深度示例:")
print(" 深度 1: 当前文件夹 + 直接子文件夹")
print(" 深度 2: 当前文件夹 + 子文件夹 + 孙文件夹")
print(" 以此类推...")


def get_user_input(prompt):
"""
获取用户输入的路径并进行标准化

Args:
prompt (str): 提示信息

Returns:
Path: 标准化后的路径对象
"""
while True:
try:
user_input = input(prompt).strip()
if not user_input:
print("错误: 路径不能为空,请重新输入")
continue

normalized_path = normalize_path(user_input)
if normalized_path is None:
print("错误: 无效的路径,请重新输入")
continue

return normalized_path

except ValueError as e:
print(f"错误: {e}")
print("请重新输入正确的路径")
continue
except KeyboardInterrupt:
print("\n程序被用户中断")
sys.exit(0)


def get_recursion_settings():
"""
获取递归设置

Returns:
tuple: (recursive, max_depth)
"""
print("\n递归设置:")
print("1. 仅当前文件夹(不递归)")
print("2. 递归所有子文件夹")
print("3. 递归指定深度")

while True:
choice = input("\n请选择递归模式 [1-3]: ").strip()

if choice == '1':
return False, None
elif choice == '2':
return True, None
elif choice == '3':
while True:
try:
depth = input("请输入递归深度(正整数): ").strip()
max_depth = int(depth)
if max_depth <= 0:
print("递归深度必须是正整数")
continue
return True, max_depth
except ValueError:
print("请输入有效的数字")
continue
else:
print("请输入 1、2 或 3")
continue


def get_user_choice():
"""获取用户选择"""
while True:
choice = input("\n是否显示详细信息(仅在某个文件夹中的文件)?[y/N]: ").strip().lower()
if choice in ['y', 'yes', '是']:
return True
elif choice in ['n', 'no', '否', '']:
return False
else:
print("请输入 y/yes/是 或 n/no/否")


def main():
"""主函数"""
print("文件夹文件名比较工具")
print("=" * 30)

try:
# 显示路径输入示例
display_path_examples()

# 获取用户输入
folder1 = get_user_input("请输入第一个文件夹的路径: ")
folder2 = get_user_input("请输入第二个文件夹的路径: ")

# 显示标准化后的路径
print(f"\n标准化后的路径:")
print(f"文件夹1: {folder1}")
print(f"文件夹2: {folder2}")

# 获取递归设置
display_recursion_examples()
recursive, max_depth = get_recursion_settings()

# 询问是否显示详细信息
show_details = get_user_choice()

# 执行比较
print("\n正在比较文件夹...")

if show_details:
common_files, only_in_folder1, only_in_folder2 = compare_folders(
folder1, folder2, show_details=True, recursive=recursive, max_depth=max_depth
)
print_results(common_files, only_in_folder1, only_in_folder2,
str(folder1), str(folder2), recursive, max_depth)
else:
common_files = compare_folders(folder1, folder2, recursive=recursive, max_depth=max_depth)
print_results(common_files, folder1_path=str(folder1), folder2_path=str(folder2),
recursive=recursive, max_depth=max_depth)

except KeyboardInterrupt:
print("\n\n程序被用户中断")
sys.exit(0)
except (FileNotFoundError, NotADirectoryError) as e:
print(f"错误: {e}")
sys.exit(1)
except Exception as e:
print(f"未知错误: {e}")
sys.exit(1)


if __name__ == "__main__":
main()

功能

  • 支持比对2个相同或不相同的目录
  • 支持无限递归所有子目录
  • 支持递归目录数量控制
  • 支持显示查找过程

使用方法

  1. 将以上代码保存为了个 python 文件,例如:folder_filename_comparator.py
  2. 在 python 环境下执行:python folder_filename_comparator.py
  3. 根据提示输入目录路径
  4. 根据提示输入查找方式
  5. 根据提示选择是否显示查找过程