资讯动态

Python批量修改文件名实例操作分享

发布时间:2026/10/17 3:27:12 来源:尧图企业网站定制
前言批量改名听起来像循环里调一下os.rename真写起来会遇到三个立刻见血的问题改成什么规则加前缀、去空格、正则替换、统一编号、改的时候会不会撞名、以及改了一半失败了怎么办。第三个问题最要命——改名是不可逆操作脚本跑到第 50 个文件崩了前 49 个已经改了剩下的一片混乱很难手工还原。本文按先想清楚命名规则 → 用 dry-run 预演 → 处理撞名 → 两阶段提交的顺序给出一份能安全跑在真实目录上的批量改名方案。全部基于标准库的pathlib与os。先说一个 Windows 上特别容易中招的点文件系统大小写不敏感。想把photo.jpg改成Photo.jpg某些情况下这个操作会被当成没有变化而失败或静默无效。稳妥做法是两阶段改名先改成一个临时名再改成目标名。一、确定改名规则命名规则通常落在这几类统一加前缀/后缀、去掉文件名里的空格或特殊字符、按正则替换片段、统一编号img_001.jpg。抽取主名 扩展名是关键一步务必用Path.stem和Path.suffix而不是自己split(.)——因为archive.tar.gz的主名是archive.tarsuffix是.gz手工切很容易切错。# 适用于 Python 3.8from pathlib import Pathp Path(archive.tar.gz)print(p.stem) # archive.tarprint(p.suffix) # .gzprint(p.suffixes) # [.tar, .gz]改名时推荐用path.with_name(新文件名)来构造目标路径它会保留父目录、只替换最后一段名字比字符串拼接安全。也可以用with_stem()/with_suffix()单独换主名或后缀这两个方法在Python 3.9才提供。二、撞名检测与两阶段提交改名最大的风险是多个旧名映射到同一个新名。比如把空格替换成下划线时a b.txt和a_b.txt会变成同一个目标。检测方式是先把整批映射算出来检查目标名是否重复、是否已存在再动手。# 适用于 Python 3.8from pathlib import Pathdef build_plan(files, rule):rule: 接收 Path返回新文件名不含目录。返回 (src, dst) 列表。plan []seen {}for f in files:new_name rule(f)dst f.with_name(new_name)if new_name in seen:raise ValueError(f目标名冲突: {f.name} 与 {seen[new_name]} 都想叫 {new_name})if dst.exists() and dst.name ! f.name:raise ValueError(f目标已存在: {dst})seen[new_name] f.nameplan.append((f, dst))return plan真正的提交分两步走第一轮把每个源文件改成一个唯一的临时名第二轮再把临时名改成目标名。这样即使两批名字互相交叉比如要把a.txt改成b.txt、b.txt改成a.txt也不会中途撞车。# 适用于 Python 3.8import osdef apply_plan(plan):两阶段改名先统一移到临时名再统一落到目标名。staged []for src, dst in plan:tmp src.with_name(f__tmp__{os.getpid()}_{src.name})os.rename(src, tmp)staged.append((tmp, dst))for tmp, dst in staged:os.rename(tmp, dst)用os.getpid()拼进临时名避免同一目录下并行跑两个实例时临时名互相覆盖。三、完整实例四类常见规则# 适用于 Python 3.8import argparseimport osimport refrom pathlib import Pathdef rule_prefix(p):return 2024_ p.namedef rule_strip_spaces(p):return re.sub(r\s, _, p.stem) p.suffixdef rule_normalize(p):把主名里的非字母数字统一替换为下划线。stem re.sub(r[^0-9A-Za-z一-鿿], _, p.stem)return stem.strip(_) p.suffix.lower()def make_number_rule():返回一个有状态的编号规则每次调用递增从 001 开始。state {n: 0}def rule(p):state[n] 1return fitem_{state[n]:03d}{p.suffix.lower()}return ruledef build_rules():return {prefix: rule_prefix,spaces: rule_strip_spaces,normalize: rule_normalize,number: make_number_rule(),}def build_plan(files, rule):plan, seen [], {}for f in files:new_name rule(f)dst f.with_name(new_name)if new_name in seen:raise ValueError(f目标名冲突: {f.name} 与 {seen[new_name]} - {new_name})if dst.exists() and dst.name ! f.name:raise ValueError(f目标已存在: {dst})seen[new_name] f.nameplan.append((f, dst))return plandef apply_plan(plan):staged []for src, dst in plan:tmp src.with_name(f__tmp__{os.getpid()}_{src.name})os.rename(src, tmp)staged.append((tmp, dst))for tmp, dst in staged:os.rename(tmp, dst)def main():rules build_rules()ap argparse.ArgumentParser(description批量重命名文件)ap.add_argument(root)ap.add_argument(--rule, choicessorted(rules), requiredTrue)ap.add_argument(--apply, actionstore_true, help真正执行默认只预演)args ap.parse_args()root Path(args.root)files [f for f in root.iterdir() if f.is_file() and not f.name.startswith(.)]files.sort(keylambda p: p.name)plan build_plan(files, rules[args.rule])for src, dst in plan:print(f{src.name} - {dst.name})if args.apply:apply_plan(plan)print(f已重命名 {len(plan)} 个文件)else:print(预演模式加 --apply 才会真正执行)if __name__ __main__:main()files的顺序依赖iterdir()的返回顺序。如果规则里用了编号最好先files.sort(keylambda p: p.name)固定顺序否则第几个文件变成 item_001每次可能不一样。需求写法注意加前缀2024_ p.name直接拼name别丢后缀替换空格re.sub(r\s, _, p.stem)只处理主名后缀单独接回统一小写后缀p.suffix.lower()后缀含点别漏按序号命名fitem_{i:03d}{p.suffix}先排序保证顺序稳定只改后缀p.with_suffix(.txt)3.9 有with_stem常见坑点❌ 用name.split(.)[0]取主名✅a.tar.gz会被切成a后缀全丢用Path.stem/Path.suffix❌ 边遍历边改名导致重复处理或漏处理✅ 先list(root.iterdir())固化成列表算出完整映射再统一执行❌ 直接os.rename而不管目标是否已存在✅ 目标存在时 POSIX 上可能静默覆盖、Windows 上可能抛错行为随平台漂移提前做存在性检查❌ 一批文件的新名字互相交叉a→b、b→a时直接一对一对改✅ 用两阶段改名先全部改成唯一临时名再改成目标名❌ 想把photo.jpg改成Photo.jpg却直接os.rename✅ 大小写不敏感的文件系统上可能被当成无变化先改临时名再改回来❌ 没有预演第一次就在真实目录上执行✅ 默认 dry-run 打印映射表确认后加--apply重要目录先备份❌ 正则替换只替换第一处或把后缀也一起改了✅re.sub默认替换全部但要用stem参与匹配最后再把suffix接回去❌ 忘了跳过隐藏文件和临时文件✅ 过滤name.startswith(.)并排除自己生成的__tmp__前缀避免二次运行把临时文件当成目标总结阶段做什么关键点规划算新名字用stem/suffix别手工切点校验查冲突与存在一处冲突就中止不部分执行执行两阶段改名临时名过渡跨平台安全兜底dry-run默认不落盘确认再动手批量改名的核心不是正则写得多花而是先算清楚再动手、冲突就整体中止、用两阶段避免交叉撞车。把这三条落实--apply这个开关按下去的时候才不会心慌。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑