查看: 230|回复: 0

Python批量重命名文件:自然排序与防覆盖脚本实现

[复制链接]
发表于 1 小时前 | 显示全部楼层 |阅读模式
批量重命名文件看似只是改名,真正落地时要拆成规则层、映射层和安全层。规则层决定新名字怎样生成;映射层解决旧文件和新文件如何一一对应;安全层处理覆盖、备份和回滚。原文强调:文件系统返回的列表顺序不一定和资源管理器显示顺序一致,直接按顺序编号很容易出现 page10 排在 page2 前面。

核心 API:os.rename 与 Path.rename

os.rename(src, dst) 传入两个路径字符串,兼容性老;pathlib 的 Path.rename(target) 更顺手,Path 对象可用 .name、.suffix、.stem、.with_name()、.with_suffix()。新代码优先 pathlib,因为 Windows 路径反斜杠在普通字符串里是转义符,而 Path('D:/new/file.txt') 对正斜杠兼容更好。Path.replace() 与 Path.rename() 不同:在 Windows 上目标文件已存在时,os.rename() 会报错,Path.replace() 会静默覆盖,必须警惕。
  1. from pathlib import Path
  2. p = Path('D:/项目照片/IMG_20250413_093021.jpg')
  3. print(p.parent)
  4. print(p.stem)
  5. print(p.suffix)
复制代码

排序:批量重命名最容易翻车的一环

字符串排序按字符比较,'page10.txt' 会排在 'page2.txt' 前面。可做自然排序,把数字段转为整数;也可按修改时间等元数据排序。永远不要假设文件系统返回顺序就是你要的顺序,要用 sort() 显式控制。
  1. import re
  2. def natural_key(name):
  3.     return [int(part) if part.isdigit() else part for part in re.split(r'(\d+)', name)]
复制代码
  1. from pathlib import Path
  2. files = list(Path('D:/照片').glob('*'))
  3. files.sort(key=lambda p: p.stat().st_mtime)
复制代码

防覆盖、格式统一、备份

执行前用 Counter 检查新名称是否批量重复;序号用 f'{idx:03d}' 零填充,避免 100 排在 2 前面;重要操作前把 old_name、new_name 写入 CSV 日志,方便回滚。dry_run=True 先试运行打印操作,确认后再改 False。
  1. from collections import Counter
  2. new_names = [generate_new_name(p) for p in files]
  3. duplicates = [name for name, count in Counter(new_names).items() if count > 1]
  4. if duplicates:
  5.     raise SystemExit(f'发现重名: {duplicates}')
复制代码

场景:按序号批量重命名

下面函数过滤子目录,只处理文件,按自然排序编号,并在目标已存在时跳过。
  1. from pathlib import Path
  2. import re
  3. def natural_key(name):
  4.     return [int(part) if part.isdigit() else part for part in re.split(r'(\d+)', name)]
  5. def rename_by_sequence(directory, new_prefix='文件', digits=3):
  6.     directory = Path(directory)
  7.     files = [p for p in directory.iterdir() if p.is_file()]
  8.     files.sort(key=lambda p: natural_key(p.stem))
  9.     for idx, file_path in enumerate(files, start=1):
  10.         new_name = f'{new_prefix}_{idx:0{digits}d}{file_path.suffix}'
  11.         new_path = file_path.with_name(new_name)
  12.         if new_path.exists():
  13.             print(f'[跳过] 目标已存在: {new_path.name}')
  14.             continue
  15.         file_path.rename(new_path)
  16.         print(f'[成功] {file_path.name} -> {new_path.name}')
复制代码

调用:rename_by_sequence('D:/照片', new_prefix='现场照', digits=3)。

场景:替换、清除关键字

str.replace() 会替换所有匹配子串,不是只换第一个;只替换第一次可传 replace(old, new, 1)。它区分大小写,若要忽略大小写可用 re.sub(r'(?i)img', '照片', file_path.stem)。
  1. from pathlib import Path
  2. def rename_replace(directory, old_text='', new_text='', remove_text=''):
  3.     directory = Path(directory)
  4.     for file_path in directory.iterdir():
  5.         if not file_path.is_file():
  6.             continue
  7.         new_stem = file_path.stem
  8.         if old_text:
  9.             new_stem = new_stem.replace(old_text, new_text)
  10.         if remove_text:
  11.             new_stem = new_stem.replace(remove_text, '')
  12.         new_name = f'{new_stem}{file_path.suffix}'
  13.         new_path = file_path.with_name(new_name)
  14.         if new_name == file_path.name:
  15.             continue
  16.         if new_path.exists():
  17.             print(f'[跳过] 目标已存在: {new_path.name}')
  18.             continue
  19.         file_path.rename(new_path)
  20.         print(f'[成功] {file_path.name} -> {new_path.name}')
复制代码

场景:用正则提取关键信息

正则适合从旧文件名提取日期、楼栋、楼层等字段再重组。尽量用 re.match() 严格从开头匹配;命名分组 (?P<date>...) 比数字分组可读性好;文件名中的点号要写成 \.,不要写成任意字符 .。
  1. from pathlib import Path
  2. import re
  3. def rename_extract(directory, pattern, new_format):
  4.     directory = Path(directory)
  5.     for file_path in directory.iterdir():
  6.         if not file_path.is_file():
  7.             continue
  8.         match = re.match(pattern, file_path.stem)
  9.         if not match:
  10.             print(f'[跳过] 不匹配规则: {file_path.name}')
  11.             continue
  12.         try:
  13.             new_stem = new_format.format(*match.groups())
  14.         except IndexError:
  15.             print(f'[错误] 格式中引用了不存在的分组: {file_path.name}')
  16.             continue
  17.         new_path = file_path.with_name(new_stem + file_path.suffix)
  18.         if new_path.exists():
  19.             print(f'[跳过] 目标已存在: {new_path.name}')
  20.             continue
  21.         file_path.rename(new_path)
  22.         print(f'[成功] {file_path.name} -> {new_path.name}')
复制代码

调用示例:
  1. rename_extract(
  2.     'D:/巡检记录',
  3.     r'(\d{4})-(\d{2})-(\d{2})_(\w+)_(\d+)层',
  4.     '{4}_{5}_{1}{2}{3}'
  5. )
复制代码

场景:时间戳归档与同秒防覆盖

按 st_mtime 生成时间戳时,同一秒内可能有多个文件。循环检查 new_path.exists(),重名时追加 01、02 计数,避免覆盖。
  1. from pathlib import Path
  2. from datetime import datetime
  3. def rename_by_time(directory, prefix='备份'):
  4.     directory = Path(directory)
  5.     for file_path in directory.iterdir():
  6.         if not file_path.is_file():
  7.             continue
  8.         mtime = file_path.stat().st_mtime
  9.         time_str = datetime.fromtimestamp(mtime).strftime('%Y%m%d_%H%M%S')
  10.         new_name = f'{prefix}_{time_str}{file_path.suffix}'
  11.         new_path = file_path.with_name(new_name)
  12.         if new_path.exists():
  13.             counter = 1
  14.             while new_path.exists():
  15.                 new_name = f'{prefix}_{time_str}_{counter:02d}{file_path.suffix}'
  16.                 new_path = file_path.with_name(new_name)
  17.                 counter += 1
  18.         file_path.rename(new_path)
  19.         print(f'[成功] {file_path.name} -> {new_path.name}')
复制代码

兼容与排查

Windows 文件名不允许包含 \ / : * ? " < > |,Linux 只不允许 / 和空字符。如果新文件名用 2025-06-01 12:30 这种带冒号格式,在 Windows 上会触发 FileNotFoundError 或 OSError,建议改成 20250601_1230。中文文件名乱码通常和脚本文件编码、终端编码有关;排序错乱则优先检查是否用了自然排序和零填充。批量操作后,可对比 rename_log.csv,或先 dry_run 打印映射关系再执行。

小结

批量重命名真正可用的结构是:对外暴露一行调用,对内完成排序、防重、备份、日志和 dry_run。Path.rename()/os.rename() 只是动作,规则层、映射层和安全层才是脚本稳定的关键。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

关注微信公众号

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2026-9-14 13:40 , Processed in 0.020398 second(s), 18 queries , Gzip On, Redis On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部