Batch File Rename CLI with Dry Run
A Node CLI that batch-renames files in a directory using a find-and-replace pattern, defaulting to a dry run that lists planned changes before any rename happens.
Project
node-batch-rename
Entry point: rename.mjs
Install
node --versionRun
node rename.mjs --dir ./photos --find IMG --replace photoTest
node --test rename.test.mjsExpected output
Lists each file to be renamed from its old name to the new name without changing anything first.Dependencies
- node >=20
What it does
Renames files by pattern, but only after showing exactly what it will do. The default is a dry run so a wrong pattern never touches the disk.
import { readdir, rename } from 'node:fs/promises';
import { join } from 'node:path';
export function planRename(names, find, replace) {
return names.map((name) => ({ from: name, to: name.split(find).join(replace) }))
.filter((p) => p.from !== p.to);
}
async function run() {
const args = Object.fromEntries(process.argv.slice(2).map((a) => a.replace(/^--/, '').split('=')));
const dir = args.dir ?? '.';
const plan = planRename(await readdir(dir), args.find, args.replace);
for (const p of plan) {
console.log(p.from + ' -> ' + p.to);
if (args.apply) await rename(join(dir, p.from), join(dir, p.to));
}
console.log(plan.length + ' file(s) ' + (args.apply ? 'renamed' : 'to rename (dry run)'));
}
run();
The plan is a list of from-to pairs, which makes the dry run and the real run share one code path and keeps the preview honest.
Run and verify
node rename.mjs --dir ./photos --find IMG --replace photo
Run without –apply first to review the plan; add –apply only after the listing looks correct.
Expected Output
Two matching files should be listed with their new names and a dry-run summary. The output is:
IMG_001.jpg -> photo_001.jpg
IMG_002.jpg -> photo_002.jpg
2 file(s) to rename (dry run)
Without –apply no file changes, and with it the same pairs are renamed; files whose name does not contain the pattern are omitted from the plan.
Limitations
The replacement is literal, not a regular expression, and collisions are not detected; two names mapping to one target would overwrite.