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
|
""" 文件夹文件名比较工具 比较两个文件夹中的文件名,找出相同和不同的文件 """
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() if os.name == 'nt': path_str = path_str.replace('/', '\\') 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()
|