How to mass rename files in Node.js
By Flavio Copes
Learn how to mass rename a set of files in Node.js with the fs module, using readdirSync, mkdirSync, and renameSync to move each file into its own folder.
In this blog post I’m going to explain how to rename a set of files.
The same process works to move files to another folder, because when you rename you rename the path of the file.
The motivation for this task was this: in Hugo we can write blog posts as files, like this:
first-post.md
second-post.md
third-post.md
We can also add them to a folder that contains an index.md file:
first-post/
> index.md
second-post/
> index.md
third-post/
> index.md
The difference is that with folder we can add images and associate them more easily to a blog post.
I could have done the change manually, but I had about 50 files in the folder and I didn’t really want to do that job.
I wanted it to happen automatically.
Let’s start by requiring one core module we’re going to use: fs. As it is a core module, there’s no need to npm install it.
const fs = require('fs')
Then, get a reference to the folder. I suppose we put the script in the same folder where we want to perform this change.
__dirname is the variable that points to the folder that contains the script. It’s not the folder you run node from, which is process.cwd().
I get a list of all the files and folders:
const files = fs.readdirSync(__dirname)
Then I filter out only the items that end with .md:
for (const file of files) {
if (file.endsWith('.md')) {
console.log(file)
}
}
Once we have the file reference, which represents the filename, we can call fs.mkdirSync() to create the folder:
fs.mkdirSync(
__dirname + '/' + file.replace('.md', ''),
{ recursive: true }
)
With recursive: true, mkdirSync() doesn’t fail if the folder already exists.
Once we create the folder, we call fs.renameSync().
The reason I use the blocking versions mkdirSync() and renameSync() instead of mkdir() and rename() is that I want to wait until the folder is created before moving the file into it. Otherwise the rename could run before the folder exists, and fail.
A blocking function finishes its work before the next line runs. It doesn’t take a callback: if something goes wrong, it throws an error and the script stops.
Node.js also has promise-based versions of these functions, which you can await. We’ll see them at the end.
The fs.renameSync() function accepts 2 parameters:
- the current path
- the path we want to move to
The current path is:
__dirname + '/' + file
The path we want to move to is:
__dirname + '/' + file.replace('.md', '') + '/index.md'
See? We create a new folder from the file name, then we append index.md:
fs.renameSync(
__dirname + '/' + file,
__dirname + '/' + file.replace('.md', '') + '/index.md'
)
Here’s the full code:
const fs = require('fs')
const files = fs.readdirSync(__dirname)
for (const file of files) {
if (file.endsWith('.md')) {
fs.mkdirSync(
__dirname + '/' + file.replace('.md', ''),
{ recursive: true }
)
fs.renameSync(
__dirname + '/' + file,
__dirname + '/' + file.replace('.md', '') + '/index.md'
)
}
}
Be careful: if a folder already contains an
index.mdfile,renameSync()replaces it without asking. Try the script on a copy of your files first.
The promise-based version
The functions in node:fs/promises return promises. If we await each one, the folder still gets created before the rename runs.
Save this version as rename.mjs. The .mjs extension tells Node.js to treat the file as an ES module, so we can use import and await at the top level. Here import.meta.dirname replaces __dirname, and path.join() builds the paths:
import { readdir, mkdir, rename } from 'node:fs/promises'
import path from 'node:path'
const folder = import.meta.dirname
const files = await readdir(folder)
for (const file of files) {
if (file.endsWith('.md')) {
const name = file.replace('.md', '')
await mkdir(path.join(folder, name), { recursive: true })
await rename(path.join(folder, file), path.join(folder, name, 'index.md'))
}
}
Run it with:
node rename.mjs
If something goes wrong, the await throws and the script stops with the error. Like renameSync(), rename() replaces an existing index.md without asking.
For a one-off script like this one, both versions work fine.
Want me to talk about your product? You can sponsor this site.
Related posts about node: