浏览 AI使用教程
批量重命名文件脚本:先预览映射、试跑 10 个再全量执行
批量重命名前先生成新旧映射,检查重名,再用少量文件试跑。真正可靠的脚本不仅能改名,还能说明改了什么以及怎样恢复。
按实际操作顺序推进
每完成一段就检查当前输出,确认结果符合预期后再继续。
600 个文件一次改名,最怕的不是失败,而是静默覆盖
两个文件可能被规则映射成同一个名称。临时文件名只能缓解先后占位,不能让同一个最终文件名容纳两份内容。
本例不处理 EXIF 日期或 600 张真实图片。目标是演示冻结映射、拒绝碰撞和反向恢复;只在副本运行。
- 多对一直接拒绝,不用临时名掩盖
- 执行前逐项核对原始哈希
- 成功日志与源副本保留
动手前,把材料和不能改的部分定下来
| 需要确认 | 本例内容 |
|---|---|
| 现有材料 | Node.js 20+;练习副本 photos-copy 中只有 img_A.jpg、img_B.jpg,可用任意公开图片副本。脚本与图片目录同级。 |
| 不能越过的边界 | 先只生成映射,不改文件;检查冲突、非法字符和缺失;复制小样本试跑;保留可逆映射。 |
| 要交付的结果 | 新旧名称映射、试跑和回滚结果 |
批量重命名文件脚本,从“建立副本”开始做
建立副本
把两张公开图片复制为 img_A.jpg、img_B.jpg,原目录不参与脚本。
只生成计划
运行 plan,逐行检查旧名、新名和哈希;已有计划不会覆盖。
识别不同碰撞
多对一必须改变最终命名规则;A→B、B→A 属占位循环,可另用临时唯一名两阶段处理,本脚本选择拒绝。
执行并核对
apply 预检所有行后才开始逐项改名;执行期间不要让其它程序修改该目录。
反向恢复
全部成功后运行 rollback;若中断则先按日志判断当前状态,不能盲目执行完整回滚。
有意限制范围的完整改名脚本
rename.mjs
冻结计划、验证、执行和恢复
import fs from 'node:fs';
import path from 'node:path';
import crypto from 'node:crypto';
const [mode, dirArg = './photos-copy'] = process.argv.slice(2);
const root = fs.realpathSync(dirArg);
const manifest = path.join(root, 'rename-plan.json');
const hash = file => crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex');
const safe = name => typeof name === 'string' && /^[a-zA-Z0-9_.-]+$/.test(name) && !['.', '..'].includes(name);
if (mode === 'plan') {
if (fs.existsSync(manifest)) throw new Error('已有计划,请先检查,不覆盖');
const rows = fs.readdirSync(root, { withFileTypes: true })
.filter(e => e.isFile() && /^img_[a-z0-9_-]+\.jpg$/i.test(e.name))
.map(e => ({ old: e.name, new: e.name.slice(4).toLowerCase(), sha256: hash(path.join(root, e.name)) }));
if (!rows.length) throw new Error('没有 img_*.jpg');
fs.writeFileSync(manifest, JSON.stringify({ root, rows }, null, 2), { flag: 'wx' });
console.log('仅生成计划,未改名', rows);
} else {
if (!['apply', 'rollback'].includes(mode)) throw new Error('用法: node rename.mjs plan|apply|rollback 目录');
const plan = JSON.parse(fs.readFileSync(manifest));
if (plan.root !== root) throw new Error('计划属于另一个目录');
const rows = plan.rows.map(r => ({ ...r, from: mode === 'apply' ? r.old : r.new, to: mode === 'apply' ? r.new : r.old }));
const targets = new Set();
for (const r of rows) {
if (!safe(r.from) || !safe(r.to)) throw new Error('名称越界');
const key = r.to.toLowerCase();
if (targets.has(key)) throw new Error('多个源指向同一目标,重新设计命名规则');
targets.add(key);
const from = path.join(root, r.from), to = path.join(root, r.to);
if (!fs.lstatSync(from).isFile() || hash(from) !== r.sha256) throw new Error('源文件变更或不是普通文件');
if (fs.existsSync(to)) throw new Error('目标已存在;本脚本不处理链式/循环改名');
}
const journal = path.join(root, 'rename-' + mode + '-' + Date.now() + '.jsonl');
for (const r of rows) {
fs.renameSync(path.join(root, r.from), path.join(root, r.to));
fs.appendFileSync(journal, JSON.stringify(r) + '\n');
}
console.log(mode + ' 完成 ' + rows.length + ' 项;核对日志 ' + journal);
}终端
按顺序执行并逐次核对
node rename.mjs plan ./photos-copy node rename.mjs apply ./photos-copy node rename.mjs rollback ./photos-copy
不要把第三条当作自动补救:仅在前一次全部成功、文件未被后续修改时运行。脚本检测内容变化、符号链接和目标占位;并发修改、磁盘故障仍需人工按日志恢复。
三种冲突不能用同一个答案
| 映射 | 问题 | 处理 |
|---|---|---|
| img_A.jpg→a.jpg;img_a.jpg→a.jpg | 多对一 | 改变最终命名,例如加入稳定序号;两份内容不能覆盖 |
| A→B;B→A | 循环占位 | 另写两阶段程序和完整日志;本例拒绝 |
| A→B;B 已存在且不在映射 | 外部目标占位 | 停止,确认归属,不覆盖 |
完成后的新旧名称映射、试跑和回滚结果
预期计划为 img_A.jpg→a.jpg、img_B.jpg→b.jpg。apply 后核对哈希,rollback 后恢复原名。多个源到同一目标、目标占位或循环改名均在任何写入前拒绝。
为什么“多人同时修改目录造成中途失败”还不能交付
多人同时修改目录造成中途失败
- 原因
- 预检与逐项执行之间并非文件系统事务
- 怎么改
- 停止并按已有日志逐项核对,不能直接重跑完整反向计划
