Is node.js rmdir recursive? Will it work with non-empty directories?

The documentation for fs.rmdir is very short and does not explain rmdir's behavior when the directory is not empty.

Q : What happens if I try to use this API to delete a non-empty directory?

+70
Sep 27 '12 at 18:10
source share
21 answers

Short answer: node.js fs.rmdir() calls POSIX rmdir() ; this will delete the empty directory or return an error. In this case, the call will call the callback function and throw an error as an exception.

The problem here is that the node.js documentation is referencing POSIX :

Introduction Node.js API Documents The file system states:

File I / O is provided by simple wrappers around standard POSIX functions.

This almost turns the question into a duplicate: Is there a list of POSIX API / functions?

The description for fs.rmdir is concise but sufficient.

Asynchronous rmdir (2).

rmdir(2) here is an implicit documentation link for rmdir() system call . The number (2) here is the old Unix man page convention to refer to Section 2 of the manual page containing kernel interfaces.

+33
Sep 27 '12 at 23:19
source share

Although using a third-party library for such a thing, I could not come up with a more elegant solution. So I ended up using the rpmraf npm module.

Install it

 npm install rimraf 

Or install it and save it in 'package.json' (other save options can be found in npm-install docs)

 npm install --save rimraf 

Then you can do the following:

 rmdir = require('rimraf'); rmdir('some/directory/with/files', function(error){}); 

Or in Coffeescript:

 rmdir = require 'rimraf' rmdir 'some/directory/with/files', (error)-> 
+93
May 17 '13 at 9:05
source share

I wrote about this problem exactly .

My previous solution below, although simple, is not preferred. The next function is a synchronous solution; while asynchronous mode may be preferred.

 deleteFolderRecursive = function(path) { var files = []; if( fs.existsSync(path) ) { files = fs.readdirSync(path); files.forEach(function(file,index){ var curPath = path + "/" + file; if(fs.lstatSync(curPath).isDirectory()) { // recurse deleteFolderRecursive(curPath); } else { // delete file fs.unlinkSync(curPath); } }); fs.rmdirSync(path); } }; 

[Change] Added lstat instead of stat to prevent errors in symbolic links

[Previous decision]

My solution for this is pretty simple to implement.

 var exec = require('child_process').exec,child; child = exec('rm -rf test',function(err,out) { console.log(out); err && console.log(err); }); 

This is downsized for this page, but the basic idea is simple; run 'rm -r' at the command prompt. If your application should work with different types of OS, put it in a function and use the if / else / command to do this.

You want to process all responses; but the idea is quite simple.

+52
Oct 06
source share

Just a small point among this group of answers, but I think it’s good to point out this.

Personally (and generally) I would prefer to use an existing library, if available, to complete the task. Using an already existing thing means for me, and especially in the open source world, the use and improvement of an already existing thing that may turn out to be in a better result than by itself (I improve what someone else has done).

In this case, with a little search, I found the fs-extra module , which should also be replaced by rimraf and responds to the need to delete directories recursively (apparently with asynchronous and synchronous versions). In addition, he has a large number of stars on github and seems to be currently supported: these two conditions, in addition to being necessary answers, make this a way to go (almost a bit for me) for me.

+7
Apr 02 '14 at 8:39
source share

fs.rmdir not recursive.

Instead, you can use the recursive module fs.readdir, such as readdirp , to find all files and directories. Then delete all files and then all directories.

For an even simpler solution, see rimraf .

+5
Sep 27 '12 at 20:57
source share

Node.js v12.10.0 added recursive option to fs.rmdir . Since fs.mkdir supports the same option starting with v10.12.0, creating and deleting a directory can be done recursively.

 $ node --experimental-repl-await # without recursive option -> error > await fs.promises.mkdir('foo/bar') Thrown: [Error: ENOENT: no such file or directory, mkdir 'foo/bar'] { errno: -2, code: 'ENOENT', syscall: 'mkdir', path: 'foo/bar' } # with recursive option -> success > await fs.promises.mkdir('foo/bar', { recursive: true }) undefined # without recursive option -> error > await fs.promises.rmdir('foo') Thrown: [Error: ENOTEMPTY: directory not empty, rmdir 'foo'] { errno: -66, code: 'ENOTEMPTY', syscall: 'rmdir', path: 'foo' } # with recursive option -> success > await fs.promises.rmdir('foo', { recursive: true }) undefined 
+5
Sep 05 '19 at 12:19
source share

Using child_process.execFile is faster .

NodeJS docs:

child_process.execFile is similar to child_process.exec (), except for it * does not execute a subshell, but rather the specified file directly.

It works. rm -rf DIR...

 var child = require('child_process'); var rmdir = function(directories, callback) { if(typeof directories === 'string') { directories = [directories]; } var args = directories; args.unshift('-rf'); child.execFile('rm', args, {env:process.env}, function(err, stdout, stderr) { callback.apply(this, arguments); }); }; // USAGE rmdir('dir'); rmdir('./dir'); rmdir('dir/*'); rmdir(['dir1', 'dir2']); 

Change I have to admit that this is not cross-platform, does not work on Windows

+3
May 04 '13 at 17:03
source share

Here is an asynchronous recursive version that works with promises. I use the "Q" library, but everyone will do with a few changes (for example, the fail function).

To use it, we need to make some simple wrappers around some of the core Node functions, namely fs.stat, fs.readdir, fs.unlink and fs.rmdir, to make them promising.

Here they are:

 function getStat(fpath) { var def = Q.defer(); fs.stat(fpath, function(e, stat) { if (e) { def.reject(); } else { def.resolve(stat); } }); return def.promise; } function readdir(dirpath) { var def = Q.defer(); fs.readdir(dirpath, function(e, files) { if (e) { def.reject(e); } else { def.resolve(files); } }); return def.promise; } function rmFile(fpath) { var def = Q.defer(); fs.unlink(fpath, function(e) { if(e) { def.reject(e); } else { def.resolve(fpath); }}); return def.promise; } function rmDir(fpath) { var def = Q.defer(); fs.rmdir(fpath, function(e) { if(e) { def.reject(e); } else { def.resolve(fpath); }}); return def.promise; } 

So here is the recursive function rm:

 var path = require('path'); function recursiveDelete(fpath) { var def = Q.defer(); getStat(fpath) .then(function(stat) { if (stat.isDirectory()) { return readdir(fpath) .then(function(files) { if (!files.length) { return rmDir(fpath); } else { return Q.all(files.map(function(f) { return recursiveDelete(path.join(fpath, f)); })) .then(function() { return rmDir(fpath); }); } }); } else { return rmFile(fpath); } }) .then(function(res) { def.resolve(res); }) .fail(function(e) { def.reject(e); }) .done(); return def.promise; } 
+2
Oct 16 '13 at 19:32
source share

Believed that this was a good excuse for diving into the source;)

From what I can tell, fs.rmdir bound to the rmdir function from unistd.h. On the POSIX page for rmdir :

The rmdir () function should remove the directory whose name is given by the track formula. A directory should only be deleted if it is an empty directory.

If the directory is not an empty directory, rmdir () will fail and set errno to [EEXIST] or [ENOTEMPTY].

+1
Sep 27 '12 at 19:56
source share

In addition to the correct no, rimraf provides a recursive delete function. It imitates rm -rf . It is also officially packaged by Ubuntu.

+1
Mar 29 '13 at 18:07
source share

I understand that this does not quite answer the question, but I think that it may be useful for someone looking here in the future (it would be for me!): I made a small fragment that allows only empty ones to be recursively deleted directories . If a directory (or any of its streaming directories) contains content inside it, it remains one:

 var fs = require("fs"); var path = require("path"); var rmdir = function(dir) { var empty = true, list = fs.readdirSync(dir); for(var i = list.length - 1; i >= 0; i--) { var filename = path.join(dir, list[i]); var stat = fs.statSync(filename); if(filename.indexOf('.') > -1) { //There are files in the directory - we can't empty it! empty = false; list.splice(i, 1); } } //Cycle through the list of sub-directories, cleaning each as we go for(var i = list.length - 1; i >= 0; i--) { filename = path.join(dir, list[i]); if (rmdir(filename)) { list.splice(i, 1); } } //Check if the directory was truly empty if (!list.length && empty) { console.log('delete!'); fs.rmdirSync(dir); return true; } return false; }; 

https://gist.github.com/azaslavsky/661020d437fa199e95ab

+1
May 13 '14 at 22:35
source share

In most of the examples that I see, there are synchronous implementations of recursively deleting the folder structure in node.

I also saw several asynchronous ones that really don't work well.

I wrote and used one completely asynchronous: https://gist.github.com/yoavniran/adbbe12ddf7978e070c0

+1
Sep 23 '14 at 6:51
source share

This function will recursively delete the directory or file that you specify synchronously:

 var path = require('path'); function deleteRecursiveSync(itemPath) { if (fs.statSync(itemPath).isDirectory()) { _.each(fs.readdirSync(itemPath), function(childItemName) { deleteRecursiveSync(path.join(itemPath, childItemName)); }); fs.rmdirSync(itemPath); } else { fs.unlinkSync(itemPath); } } 

I have not tested this function behavior if:

  • item does not exist, or
  • the item could not be deleted (for example, due to a permission problem).
0
Oct 29 '13 at 15:39
source share

Recursive delete directory for Node.js

It turned out that the Node.js fs module does not have a method for recursively deleting a directory and its contents. Instead, you should go through the directory structure and delete the atomic elements, i.e. separate files and empty directories. So I found the beautiful Takuo Kihira in https://gist.github.com/2367067 made in JavaScript and decided to make it a version of CoffeeScript:

0
Aug 12 '14 at 2:17
source share

I tried to make it safe, since deleting synchronization will result in an error if a file or directory is used at this time.

  var path = require('path'); var fs = require('fs') var dumpDirs = function (dir, name, cb) { fs.readdir(dir, function (err, files) { var dirs = [], filePath, i = 0, l = files.length; for (var i = 0; i < l; i++) { filePath = path.join(dir, files[i]); var stats = fs.lstatSync(filePath); if (stats.isDirectory()) { if (files[i].indexOf(name) != -1) { dirs.push({ startOn: new Date(stats.ctime), instance: files[i], name: name }) } } } cb(dirs); }); } var removeDir = function (dir, callback) { fs.readdir(dir, function (err, files) { c = files.length; (function remfile(i, cb) { if (i >= c) return cb(); var p = path.join(dir, files[i]) fs.unlink(p, function (err) { if (err) console.log(err); remfile(i + 1, cb) }); })(0, function () { fs.rmdir(dir, function (err) { callback() }); }); //for (var i = 0; i < c; i++) { // fs.unlinkSync(path.join(dir, files[i])); //}; }); } dumpDirs(maindir, function (dirs) { if (dirs && dirs.length > 0) { (function rem(i, cb) { if (i >= dirs.length) { return cb(); } var folder = path.join(dump, dirs[i].instance); removeDir(folder, function () { rem(i + 1, cb); }); })(0, function () { callback(); }) } else { callback(); } }); 
0
Sep 01 '14 at 13:09 on
source share

Here is a coffee script prototype that I created for fluentnode that deletes a folder recursively

 String::folder_Delete_Recursive = -> path = @.toString() if path.exists() for file in path.files() curPath = path.path_Combine(file) if curPath.is_Folder() curPath.folder_Delete_Recursive() else curPath.file_Delete() fs.rmdirSync(path); return path.not_Exists() 

here is the test:

 it 'folder_Create and folder_Delete' , -> tmpDir = "./".temp_Name_In_Folder() expect(tmpDir.folder_Exists()).to.be.false expect(tmpDir.folder_Create()).to.equal(tmpDir.realPath()) expect(tmpDir.folder_Exists()).to.be.true expect(tmpDir.folder_Delete()).to.be.true expect(tmpDir.folder_Exists()).to.be.false it 'folder_Delete_Recursive' , -> tmpDir = "./" .temp_Name_In_Folder().folder_Create() tmpFile = tmpDir.temp_Name_In_Folder().file_Create() expect(tmpDir.folder_Delete_Recursive()).to.be.true 
0
Oct 22 '14 at 8:09
source share

Pure synchronous version of rmdirSync.

 /** * use with try ... catch ... * * If you have permission to remove all file/dir * and no race condition and no IO exception... * then this should work * * uncomment the line * if(!fs.exists(p)) return * if you care the inital value of dir, * */ var fs = require('fs') var path = require('path') function rmdirSync(dir,file){ var p = file? path.join(dir,file):dir; // if(!fs.exists(p)) return if(fs.lstatSync(p).isDirectory()){ fs.readdirSync(p).forEach(rmdirSync.bind(null,p)) fs.rmdirSync(p) } else fs.unlinkSync(p) } 

And parallel IO, asynchronous version of rmdir. (Faster)

 /** * NOTE: * * If there are no error, callback will only be called once. * * If there are multiple errors, callback will be called * exactly as many time as errors occur. * * Sometimes, this behavior maybe useful, but users * should be aware of this and handle errors in callback. * */ var fs = require('fs') var path = require('path') function rmfile(dir, file, callback){ var p = path.join(dir, file) fs.lstat(p, function(err, stat){ if(err) callback.call(null,err) else if(stat.isDirectory()) rmdir(p, callback) else fs.unlink(p, callback) }) } function rmdir(dir, callback){ fs.readdir(dir, function(err,files){ if(err) callback.call(null,err) else if( files.length ){ var i,j for(i=j=files.length; i--; ){ rmfile(dir,files[i], function(err){ if(err) callback.call(null, err) else if(--j === 0 ) fs.rmdir(dir,callback) }) } } else fs.rmdir(dir, callback) }) } 

In any case, if you want the serial IO and callback to be called exactly once (either success, or with the first error encountered). Replace this rmdir above. (Slower)

 function rmdir(dir, callback){ fs.readdir(dir, function(err,files){ if(err) callback.call(null,err) else if( files.length ) rmfile(dir, files[0], function(err){ if(err) callback.call(null,err) else rmdir(dir, callback) }) else fs.rmdir(dir, callback) }) } 

All of them depend ONLY on node.js and should be portable.

0
Nov 06 '14 at
source share
 var fs = require('fs'); fs.delR = function(dir){ var s = fs.lstatSync(dir); if(s.isFile()) fs.unlinkSync(dir); if(!s.isDirectory()) return; var fileArr = fs.readdirSync(dir); for(f in fileArr) fs.delR(dir+'/'+fileArr[f]); fs.rmdirSync(dir); } 
0
Dec 10 '15 at 7:53
source share

This post received the main response from google, but none of the answers gave a solution that:

  • does not use synchronization functions

  • no external libraries required

  • doesn't use bash directly

Here is my async solution that does not assume anything but node:

 const fs = require('fs'); const path = require('path'); function rm(path){ return stat(path).then((_stat) => { if(_stat.isDirectory()){ return ls(path) .then((files) => Promise.all(files.map(file => rm(Path.join(path, file))))) .then(() => removeEmptyFolder(path)); }else{ return removeFileOrLink(path); } }); function removeEmptyFolder(path){ return new Promise((done, err) => { fs.rmdir(path, function(error){ if(error){ return err(error); } return done("ok"); }); }); } function removeFileOrLink(path){ return new Promise((done, err) => { fs.unlink(path, function(error){ if(error){ return err(error); } return done("ok"); }); }); } function ls(path){ return new Promise((done, err) => { fs.readdir(path, function (error, files) { if(error) return err(error) return done(files) }); }); } function stat(path){ return new Promise((done, err) => { fs.stat(path, function (error, _stat) { if(error){ return err(error); } return done(_stat); }); }); } } 
0
Jun 26 '17 at 14:12
source share

Following @geedew answer.

Here is the asynchronous implementation of rm -r (i.e. you can pass the path to a file or directory). I am not an experienced nodejs developer and appreciate any suggestions or constructive criticism.

 var fs = require('fs'); function ResultsCollector (numResultsExpected, runWhenDone) { this.numResultsExpected = numResultsExpected, this.runWhenDone = runWhenDone; this.numResults = 0; this.errors = []; this.report = function (err) { if (err) this.errors.push(err); this.numResults++; if (this.numResults == this.numResultsExpected) { if (this.errors.length > 0) return runWhenDone(this.errors); else return runWhenDone(); } }; } function rmRasync(path, cb) { fs.lstat(path, function(err, stats) { if (err && err.code == 'ENOENT') return cb(); // does not exist, nothing to do else if (err) { return cb(err); } if (stats.isDirectory()) { fs.readdir(path, function (err, files) { if (err) return cb(err); var resultsCollector = new ResultsCollector(files.length, function (err) { if (err) return cb(err); fs.rmdir(path, function (err) { if (err) return cb(err); return cb(); }); }); files.forEach(function (file) { var filePath = path + '/' + file; return rmRasync(filePath, function (err) { return resultsCollector.report(err); }); }); }); } else { // file. // delete file or link fs.unlink(path, function (err) { if (err) return cb(err); return cb(); }); } }); }; 

call like this:

 rmRasync('/path/to/some/file/or/dir', function (err) { if (err) return console.error('Could not rm', err); // else success }); 
0
Jun 05 '18 at 8:00
source share

Amazingly verbose and bad answers here ...

To remove a non-empty directory on most systems:

 import * as cp from 'child_process'; const dir = '/the/dir/to/remove'; const k = cp.spawn('bash'); k.stdin.end('rm -rf "${dir}"'); k.once('exit', code => { // check the exit code // now you are done }); 

This will work on MacOS and Linux, but may not work on some Windows OS.

0
Jul 13 '18 at 3:41
source share



All Articles