ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

批量修改与删除文件名的Python、Shell、Windows完整实战指南

2026/10/5 14:05:14 拓冰建站 浏览量
批量修改与删除文件名的Python、Shell、Windows完整实战指南 平时收集素材、整理照片、归档文档的时候最烦的就是文件名乱七八糟。我见过最夸张的一次朋友从网盘下载了一整个项目的参考图两千多个文件命名全是IMG_20231015_093021.jpg、微信图片_20230918_102534.jpg、untitled_folder_0035.png这种想找某一张图简直大海捞针。手动一个个改改两百个就崩溃了。这种时候批量修改和批量删除文件名的脚本就是唯一靠谱的解法。这篇文章我会从实际需求出发把批量处理文件名的完整思路讲清楚什么时候值得写脚本、Python和Shell分别怎么搞、Windows下有哪些坑、以及我亲手踩过之后总结出的几条防翻车经验。无论你平时用Windows还是Linux只要照着文章里的思路走一遍下次再遇到命名灾难就不会慌了。1. 先从最常见的三类需求说起清理、改名、归档1.1 什么样的场景值得动用脚本先说结论文件数量超过二十个、且命名规则能用一句话描述清楚的时候就值得写脚本。比如把所有.JPG后缀改成小写把文件名里的1这种重复标记去掉删除所有*.tmp临时文件。这类需求手动操作太慢单纯的搜索替换工具又不一定支持批量脚本是最划算的。我这几年的实际体验是批量处理文件名的需求大多是三类改名类调整前缀后缀、统一大小写、替换乱码、按规则重新编号。典型场景是相机照片按日期批量重命名。删除类清理临时文件、缓存文件、重复下载的文件。典型场景是下载目录每周都要清一次。归档类把散乱的文件按项目名_日期这种格式统一命名方便后续检索。这三类需求背后的逻辑是相通的先拿到文件清单再制定规则最后执行操作。搞懂这一套其他场景都是换汤不换药。1.2 批量处理文件名的核心路径清单、规则、演练、执行我自己写这类脚本永远遵循四步走生成清单遍历目标目录把所有文件名列出来。制定规则把我想怎么改翻译成正则表达式或条件判断。演练dry-run不真正改动文件只打印会做什么操作确认无误。执行正式运行并记录操作日志。很多人一上来就直接写mv或os.rename把文件改了结果发现规则写错几百个文件名被改坏又没有备份那种绝望我体会过。所以现在不管脚本多简单我都会先加一个演练模式跑一遍看看输出再决定要不要真动手。1.3 演练模式为什么会成为我的安全底线举一个很简单的例子你想删除文件名里含副本两个字的文件但没先跑演练就把find命令执行了结果发现系统里有几百个副本开头的文档其实是有用的想恢复只能靠文件历史记录大概率找不回来。演练模式实现也非常简单Python里的做法是# 修改时先打印不执行 def rename_file(src, dst): print(f[DRY RUN] {src} - {dst}) # actual_rename(src, dst) # 确认后再放开这行Shell里对应的是rename -nPowerShell里是-WhatIf参数。这几个选项我会在下面每个方案里专门讲到因为它们才是真正防止翻车的核心。2. Python方案跨平台能力最平衡的万能胶2.1 用pathlib而不是os.path的核心理由Python做文件批处理很多人还在用os.listdir加os.path.join的组合我强烈建议换成pathlib。理由很简单Path对象把路径当成对象来操作代码可读性高一大截而且跨平台表现更稳定。比如遍历某个目录下的所有文件from pathlib import Path folder Path(D:/素材/参考图) for f in folder.iterdir(): if f.is_file(): print(f.name)对比os.listdirpathlib不用拼接字符串也不用担心Windows反斜杠和Linux斜杠的差异。Path.glob还直接支持通配符和递归遍历for f in folder.glob(*.jpg): # 只找jpg ... for f in folder.rglob(*): # 递归所有文件 ...实际用下来pathlib在Windows上的表现尤其顺畅不用去手动处理盘符和斜杠问题。2.2 用正则批量修改文件名的两个高频案例案例一去掉文件名里的乱码前缀。比如这批文件长得像【URGENT】2023年度规划.pdf你想把【URGENT】去掉from pathlib import Path import re folder Path(D:/归档) for f in folder.iterdir(): if f.is_file(): new_name re.sub(r^【URGENT】, , f.name) if new_name ! f.name: print(f{f.name} - {new_name}) # f.rename(f.with_name(new_name)) # 真改时放开案例二给所有文件批量加序号前缀。注意这里要用enumerate保证序号是递增的同时按文件名排个序否则顺序会乱from pathlib import Path folder Path(D:/待整理) files sorted(folder.iterdir(), keylambda x: x.name) for idx, f in enumerate(files, start1): if f.is_file(): new_name f{idx:03d}_{f.name} print(f{f.name} - {new_name}) # f.rename(f.with_name(new_name))关于正则这块我的经验是先小范围测试正则匹配结果再应用到大目录。可以在交互环境里把正则跑一遍匹配错了立刻调整不会影响真实文件。2.3 批量删除文件的过滤条件写法删除比改名更需要谨慎所以过滤条件通常会加多个维度。比如我只想删除目录下所有满足文件大小小于1KB的.tmp文件from pathlib import Path folder Path(D:/缓存) for f in folder.rglob(*.tmp): if f.is_file() and f.stat().st_size 1024: print(fDELETE {f}) # f.unlink()也可以组合多个条件按扩展名、按文件名关键词、按修改时间。比如删除所有文件名包含copy或文件修改时间超过180天的文件import time from pathlib import Path cutoff time.time() - 180 * 86400 folder Path(D:/素材) for f in folder.iterdir(): if f.is_file(): mtime f.stat().st_mtime if copy in f.name.lower() or mtime cutoff: print(fDELETE {f}) # f.unlink()注意rglob是递归子目录的如果你只想处理当前目录用iterdir就够了。这地方一马虎可能会连子目录里的东西一起删掉。2.4 中文文件名乱码怎么修热词里很多人搜文件名乱码修复我猜大多数是Windows下互相拷贝文件或者从压缩包里解压出来的文件名变成了一堆锟斤拷之类的乱码。这类问题本质是编码错位文件名的字节是用A编码写入的系统用B编码来解码结果显示的字符就乱了。Python里处理这类问题思路是把乱码的文件名重新按错误的编码解码一次再按正确的编码编码回去。比如本来是UTF-8编码、被系统按GBK读了可以这样恢复from pathlib import Path folder Path(D:/修复) for f in folder.iterdir(): if f.is_file() and 锟 in f.name: # 典型乱码特征 try: # 把当前文件名按GBK编码还原字节再用UTF-8解码 fixed_name f.name.encode(gbk).decode(utf-8) print(f{f.name} - {fixed_name}) # f.rename(f.with_name(fixed_name)) except (UnicodeEncodeError, UnicodeDecodeError) as e: print(f跳过 {f.name}: {e})完全不知道原来编码的情况下多试几种组合就行常见的就GBK/UTF-8/Latin-1这些。我没法保证百分百修复但能修复的比例相当高。真正的治本方案是以后压缩文件时统一选UTF-8编码。3. Linux Shell方案一条命令解决九成场景3.1 rename命令的两个流派别搞混了Linux下rename命令其实有完全不同的两种踩过坑的人都知道。Debian/Ubuntu系用的是Perl版本支持正则RedHat/CentOS系用的是简化版本只能做简单的替换。Perl版本Ubuntu/Debian的用法是# 把所有的txt后缀改成md rename s/\.txt$/.md/ *.txt # 把所有文件名里的空格替换成下划线 rename s/ /_/g *.txt # -n参数是演练模式不真正执行 rename -n s/ /_/g *.txt简化版本CentOS/RedHat的用法是# 把文件名里的foo替换成bar rename foo bar *.txt我的建议是不确定自己系统是哪个版本时先跑rename --version看到Perl字样就用正则版本否则用简化版本。这个步骤不花十秒能省去大量改名失败的麻烦。3.2 for循环加参数展开批量改名有些场景用rename的正则反而不直观比如给文件加序号或者去掉扩展名。这种我用for循环加Shell参数展开来实现功能非常灵活。# 把所有.JPG后缀改成小写.jpg for f in *.JPG; do mv $f ${f%.JPG}.jpg done这里${f%.JPG}的意思是从右边去掉.JPG后缀同理${f#prefix}是从左边去掉前缀${f/old/new}是替换中间内容。这套参数展开语法我在写shell脚本时天天用比sed还顺手。再举个批量加日期前缀的例子假设文件叫report1.pdf到report99.pdffor f in report*.pdf; do mv $f 20250412_$f done有一个地方必须强调$f的引号不能省略。如果文件名里有空格不加引号的话mv会把一个文件名拆成多个参数轻则报错重则把文件改名改飞了。3.3 find配合批量删除的正确姿势批量删除我基本用find而不是rm加通配符因为find支持的条件更精确还能在删除前预览。# 删除当前目录下所有.tmp文件 find . -name *.tmp -delete # 删除之前先列出确认范围 find . -name *.tmp -print # 删除所有大于100M的文件 find . -type f -size 100M -delete # 删除超过30天未修改的log文件 find . -name *.log -mtime 30 -delete关于find配合-delete我的习惯是永远先跑一次不加-delete的版本看到输出确认无误再补上-delete执行。反正命令一模一样只是少了删除选项多花几秒钟而已。这里要特别提醒一个坑find . -name *.tmp -delete是从当前目录递归往下的。如果你在最顶层目录执行整个目录树里所有.tmp都会没掉有时候连临时备份文件也被误删恢复都没得恢复。3.4 写一个带传参的批量处理脚本如果需求不止一次建议写成独立脚本比如rename_tool.sh#!/bin/bash set -euo pipefail # 用法: ./rename_tool.sh 原后缀 新后缀 目录 old_ext$1 new_ext$2 target_dir${3:-.} cd $target_dir for f in *.$old_ext; do [ -e $f ] || continue # 避免无匹配时进入循环 base${f%.$old_ext} mv $f $base.$new_ext echo RENAME: $f - $base.$new_ext done脚本里我习惯加set -euo pipefail这行意思是脚本遇到任何错误就退出、变量没定义就报错、管道中间的命令出错也整体失败。写了这行之后脚本行为更可控不容易在某个命令失败之后继续往下跑把后面的文件搞坏。4. Windows环境BAT脚本和PowerShell怎么选4.1 BAT脚本对付简单需求其实够用Windows下最简单的批量改名用ren命令就能完成。比如把当前目录所有.txt改成.mdren *.txt *.md但ren一次只能做简单的通配替换想批量加前缀或者做正则替换就力不从心了。这时要用到for循环echo off chcp 65001 nul for %%f in (*.jpg) do ( ren %%f photo_%%f ) echo 处理完成注意BAT脚本里的for变量是%%f在命令行直接敲则是%f这个双百分号是新手最容易踩的坑。另外chcp 65001 nul是为了切换UTF-8编码否则脚本里如果有中文执行时容易显示成乱码。4.2 PowerShell才是干重活的那个比BAT更强的是PowerShell生态更现代原生支持正则还有-WhatIf演练参数。比如批量把文件名里的空格换成下划线Get-ChildItem D:\素材 -File | Where-Object { $_.Name -match } | ForEach-Object { $newName $_.Name -replace , _ Rename-Item -Path $_.FullName -NewName $newName -WhatIf }这里-WhatIf就是演练模式会打印将执行什么操作但不真正改动。你看到输出没问题去掉-WhatIf再跑一遍就正式改好了。这个参数我觉得是PowerShell比BAT最明显的优势。批量删除文件同理Get-ChildItem D:\下载 -File -Recurse | Where-Object { $_.Extension -eq .tmp } | Remove-Item -WhatIf-Recurse参数类似Linux的find递归会连子目录一起处理用的时候心里要有数。4.3 Windows文件名编码的特殊坑Windows环境处理中文文件名的麻烦主要在两点一是命令行的代码页。BAT脚本里中文乱码通常就是代码页不对先用chcp 65001切到UTF-8再执行乱码会缓解很多。二是PowerShell的默认编码。在Windows PowerShell 5.1里Get-ChildItem拿到的中文文件名一般没问题但如果你在脚本里写死了中文字符串去比较可能因为脚本文件本身的编码格式导致匹配失败。解决办法是把脚本保存为UTF-8 with BOM或者在脚本开头写上$OutputEncoding [System.Text.Encoding]::UTF8另外Windows比Linux更经常遇到文件名超长的问题路径超过260字符批量改名时如果新名字太长Rename-Item直接报错。遇到这种情况要么缩短命名规则要么开启系统长路径支持这是一个经常被忽略的坑。5. 三个我实际跑过的实战案例5.1 手机照片按拍摄日期批量重命名手机导出的照片通常是IMG_20231015_093021.jpg格式命名里有日期时间但不够直观而且不同来源的文件混在一起时顺序很乱。我的方案是提取中间的日期和时间重新组织成20231015_093021_IMGxxx.jpg这种格式from pathlib import Path import re folder Path(D:/照片/待整理) pattern re.compile(rIMG_(\d{8})_(\d{6})) for f in folder.glob(IMG_*.jpg): m pattern.search(f.name) if m: date_part, time_part m.groups() new_name f{date_part}_{time_part}_{f.name} print(f{f.name} - {new_name}) # f.rename(f.with_name(new_name))跑演练的时候我看了一下三千多张照片里有一百来张命名规则不太一样可能是连拍或者后期编辑过的这些被正则跳过了没有被改动。这种少做比多做更重要至少不会把特殊文件改坏。5.2 每周清理下载目录的缓存和重复文件我的下载目录常年堆着.crdownload、.tmp、.part这类半成品文件还有一些浏览器自动保存的重复文件。清理脚本的核心逻辑是from pathlib import Path folder Path(D:/下载) kill_exts {.crdownload, .part, .tmp} for f in folder.rglob(*): if f.is_file() and f.suffix.lower() in kill_exts: print(fDELETE {f}) # f.unlink()这个脚本我一直没把unlink那行的注释去掉每次只执行打印。看了几轮输出之后会发现.tmp文件有时候不是无用的是一些软件运行时的锁文件删了可能导致正在运行的程序出问题。所以最终我删的时候只保留了.crdownload和.part两类宁可多留几天也不想误删。5.3 大量文档规范化归档有一次处理公司文件归档大量文档叫2023年度报告 最终版v3(2).docx、最终稿-FINAL-2023-报告.doc这种混乱得很。我用Python写了一个清洗规则把所有冗余内容都去掉from pathlib import Path import re folder Path(D:/归档/待整理) for f in folder.iterdir(): if f.is_file() and f.suffix.lower() in {.doc, .docx, .pdf}: # 去掉空白、括号序号、最终版、FINAL、v3等冗余标记 name f.stem name re.sub(r\s*\((\d)\)\s*$, , name) name re.sub(r[(]\d[)], , name) name re.sub(r最终版|最终稿|FINAL|final|v\d, , name) name re.sub(r\s, , name).strip() new_name name f.suffix.lower() print(f{f.name} - {new_name}) # f.rename(f.with_name(new_name))实测跑下来文档名从2023年度报告 最终版v3(2).docx变成了2023年度报告.docx。这类清洗最需要注意的是正则的贪婪匹配比如(2)这种序号夹在文件名的中间如果正则写得太宽容易把正文里有用的数字也去掉。先跑演练看输出匹配不对马上调。6. 我的几条防翻车经验都是拿惨痛教训换的6.1 动手之前先做清单备份这里说的备份不是复制文件本身而是把完整文件清单导出成文本。比如在命令行运行ls -laR filelist.txt或者用PowerShellGet-ChildItem -Recurse | Select-Object FullName filelist.txt万一改坏了这份清单至少能告诉你原来的文件名是什么。我甚至建议在改名前把旧文件名到新文件名的映射关系也存下来恢复的时候就是现成的mv脚本。更好的备份方式是用Git管理文件目录改动前git init git add . git commit改完之后后悔了直接git checkout回来。这个思路尤其适合处理代码项目里的资源文件。6.2 特殊字符是最大的隐形杀手文件名里的空格、括号、、单引号、中文标点每个都能让脚本崩溃。最常见的两个场景Shell里变量没加双引号mv $f newname变成了mv file 1.txt newname直接报错。Windows文件名里包含BAT脚本会把后面的内容当成另一条命令执行。处理办法其实很简单给变量统一加双引号正则里遇到特殊字符要记得转义。另外文件名末尾的隐藏空格和全角空格也很难察觉调试的时候可以先repr或者cat -A看原始字符。6.3 别动系统文件和隐藏文件批量遍历目录的时候一定要排除.git、.env、node_modules这些系统和配置文件目录。递归删除时更是如此一个手滑把.git目录删了代码版本历史就全没了。skip_dirs {.git, .svn, node_modules, __pycache__} for f in folder.rglob(*): if any(part in skip_dirs for part in f.parts): continue ...同理别用管理员权限跑批量删除脚本日常用户权限已经够了。权限越高误操作的范围越大。6.4 批量操作之后如何快速验证处理完文件我会做三个快速检查总数核对处理前后文件数量是否一致删除操作除外。随机抽查打开几个改名后的文件确认能正常打开名字和内容对得上。复查日志检查脚本输出的每一条记录看有没有明显的异常目标。之前有过一次经验一个批量改名脚本跑完文件数量没错但抽查时发现有个PDF文件被改了名字内容却是另一份文档。原因是当初这个PDF原本就是从别的地方复制来的文件名和内容本来就不匹配。这种历史遗留问题脚本不会自动纠正只能靠抽查发现。6.5 不要用脚本处理带软链接、快捷方式的目录pathlib的rglob默认会把符号链接目录也展开如果不小心可能顺着软链接跑到完全不相干的目录里做了大量改名操作。Windows下的快捷方式.lnk同样要小心它本身也是一个文件重命名后的路径关系有时会失效。我的做法是明确排除链接目录在Python里判断f.is_symlink()在Shell里用find加-type f也可以过滤掉符号链接但要注意不同系统对-type f处理链接的方式不完全一样最好先ls -l看一眼链接指向哪里再决定。写在最后的个人体会批量修改和删除文件名这件事技术难度其实不大真正的难点在于想清楚你要干什么。我见过不少人花了一天时间写了个极其复杂的正则最后才发现需求本身不明确改出来反而更乱。所以我的习惯是先花五分钟明确规则再花十分钟写脚本最后花三分钟跑演练确认结果符合预期再执行。如果你刚接触这块建议从Python加pathlib开始把本文里的第一个示例改一改找一个几十个文件的目录练手。先跑演练模式确认没问题了再放开真正的改动动作。等你熟练了就会发现批量处理文件名再也不是什么痛苦环节顺手就处理了。