是node.js rmdirrecursion? 它会在非空目录上工作吗?

fs.rmdir的文档非常短,并不能解释当目录不为空时rmdir的行为。

:如果我尝试使用此API删除非空目录,会发生什么情况?

简短的回答: node.js fs.rmdir()调用POSIX rmdir() ; 这将删除一个空目录,或返回一个错误 。 在给定的情况下,调用将调用callback函数并将错误作为例外传递。

这里的问题是node.js文档引用POSIX :

Node.js API Docs 文件系统介绍说:

文件I / O由标准POSIX函数的简单包装提供。

这几乎将问题变成了以下复本: 是否有POSIX API /函数的列表?

fs.rmdir的描述是简洁的,但是足够了。

asynchronousrmdir(2)。

这里的rmdir(2)是对rmdir() system call文档的隐式引用。 这里的数字(2)是一个旧的unix手册页约定,指示包含内核接口的手册页的第2部分。

虽然使用第三方库这样的事情,我不能拿出一个更优雅的解决scheme。 所以我最终使用了npm-module rimraf 。

安装它

 npm install rimraf 

或者安装并保存到“package.json”(其他保存选项可以在npm-install文档中find)

 npm install --save rimraf 

然后你可以做到以下几点:

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

或者在Coffeescript:

 rmdir = require 'rimraf' rmdir 'some/directory/with/files', (error)-> 

我完全写了关于这个问题 。

我以前的解决scheme虽然简单,但并不是首选。 以下function是同步解决scheme; 而asynchronous可能是首选。

 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); } }; 

[编辑]添加lstat而不是stat来防止符号链接上的错误

[以前的解决scheme

我的解决scheme很容易实现。

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

这个页面减less了,但基本的想法很简单, 在命令行上执行'rm -r'。 如果你的应用程序需要运行在不同types的操作系统上,把它放在一个函数中,并有一个if / else /开关来处理它。

你会想要处理所有的回应; 但这个想法很简单。

fs.rmdir 不是recursion的。

您可以改为使用像readdirp这样的recursionfs.readdir模块来查找所有文件和目录。 然后删除所有文件,然后删除所有目录。

对于一个更简单的解决scheme,看看rimraf 。

在这一系列的答案中,只有一小点,但我认为指出它是很好的。

个人(和一般)我宁愿使用已有的图书馆,如果有一个可用的,做任务。 拿一个已经存在的东西意味着,对于我,特别是在开源世界中,使用和改进一个已经存在的东西,最终会比我自己做的更好(我正在改进某些其他东西完成)。

在这种情况下,通过一个小的search,我发现了模块fs-extra ,它的目标是replacerimraf并回答需要删除recursion目录(显然是使用asynchronous和同步版本)。 而且,它已经在github上得到了很多明星,而且现在看起来已经很完美了:这两个条件,除了这个需求的答案之外,对我来说也是一个很好的select。

使用child_process.exec文件更快

NodeJS文档:

child_process.execFile类似于child_process.exec(),除了它不直接执行子shell而是执行指定的文件。

这工作。 模仿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']); 

编辑 :我不得不承认,这是不是跨平台,不会在Windows上工作

这是一个与promise一起工作的asynchronousrecursion版本。 我使用'Q'库,但任何人都会做一些改变(例如“失败”function)。

要利用它,我们必须围绕一些核心的Node函数,即fs.stat,fs.readdir,fs.unlink和fs.rmdir做一些简单的包装,以使它们保证友好。

他们来了:

 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; } 

所以这里是recursion的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; } 

认为这是一个很好的借口,潜入源头;)

从我所知道的, fs.rmdir绑定到unistd.h的rmdir函数。 从rmdir的POSIX手册页 :

rmdir()函数将删除一个名称由path给出的目录。 只有当目录是空目录时,该目录才能被删除。

如果目录不是空目录,则rmdir()将失败并将errno设置为[EEXIST]或[ENOTEMPTY]。

除了正确的“否”答案之外, rimraf软件包还提供了recursion删除function。 它模仿rm -rf 。 它也正式由Ubuntu 打包 。

我意识到这并不是完全回答了这个问题,但是我认为这对未来在这里search的人可能是有用的(这本来就是我的!):我做了一个小小的片段,允许recursion删除空的目录 。 如果一个目录(或其任何后代目录)里面有内容,它将被单独留下:

 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

我看到的大部分示例都是recursion删除节点中的文件夹结构的同步实现。

我也看到了一些不实际工作的asynchronous版本。

我写和使用一个完全asynchronous的: https : //gist.github.com/yoavniran/adbbe12ddf7978e070c0

这个函数将recursion地删除你指定的目录或文件,同步:

 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); } } 

我没有testing过这个函数的行为,如果:

  • 该项目不存在,或
  • 该项目不能被删除(例如由于权限问题)。

recursion移除Node.js的目录

事实certificate,Node.js fs模块没有一个方法来删除目录及其内容recursion。 相反,你应该通过目录结构,并删除primefaces项目,即单个文件和空目录。 所以我在https://gist.github.com/2367067上find了Takuo Kihira的一个很好的要点,并决定做一个CoffeeScript的版本:

试图使它失败安全,因为同时删除将导致错误,如果当时正在使用文件或目录。

  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(); } }); 

这是我为fluentnode创build的咖啡脚本原型函数,它recursion地删除一个文件夹

 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() 

这里是testing:

 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 

一个完整的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) } 

和一个并行IO,rmdir的asynchronous版本。 (更快)

 /** * 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) }) } 

无论如何,如果你想要一个顺序的IO,并且只需要调用一次callback(成功或遇到第一个错误)。 用上面的代替这个rmdir。 (比较慢)

 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) }) } 

所有这些都只依赖于node.js,并且应该是可移植的。

 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); } 

这篇文章得到了谷歌最好的答案,但没有答案给出了一个解决scheme:

  • 不使用同步function

  • 不需要外部库

  • 不直接使用bash

这是我的async解决scheme,不承担其他任何比安装的节点:

 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); }); }); } }