← Runnable Projects

阅读中文版本

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.

beginnerjavascriptunverified

Project

node-batch-rename

Entry point: rename.mjs

Install

node --version

Run

node rename.mjs --dir ./photos --find IMG --replace photo

Test

node --test rename.test.mjs

Expected 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.