fs.rmdir的文档非常简短,并且没有解释 rmdir 在目录不为空时的行为。
问:如果我尝试使用此 API 删除非空目录会怎样?
虽然使用第三方库来做这样的事情,但我想不出更优雅的解决方案。所以我最终使用了 npm-module rimraf。
安装它
npm install rimraf
或者安装它并保存到“package.json”(其他保存选项可以在npm-install文档中找到)
npm install --save rimraf
然后您可以执行以下操作:
rmdir = require('rimraf');
rmdir('some/directory/with/files', function(error){});
或者在 Coffeescript 中:
rmdir = require 'rimraf'
rmdir 'some/directory/with/files', (error)->
下面我之前的解决方案虽然简单,但不是首选。以下函数,是一个同步解决方案;而异步可能是首选。
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 以防止符号链接上的错误
[上一个解决方案]
我的解决方案很容易实现。
var exec = require('child_process').exec,child;
child = exec('rm -rf test',function(err,out) {
console.log(out); err && console.log(err);
});
这个页面被精简了,但基本的想法很简单;在命令行上执行“rm -r”。如果您的应用程序需要在不同类型的操作系统上运行,请将其放在一个函数中并使用 if/else/switch 来处理它。
您将要处理所有响应;但这个想法很简单。
简短回答:node.js fs.rmdir()
调用 POSIX rmdir()
;这将删除一个空目录,或者返回一个错误。在给定的情况下,调用将调用回调函数并将错误作为异常传递。
这里的问题是 node.js 文档引用了POSIX:
Node.js API 文档 文件系统API 最初是
标准 POSIX 函数的简单包装器。
这几乎将问题变成了以下问题的副本: 是否有 POSIX API / 函数的列表?
的描述fs.rmdir
很简洁,但足够了。
异步 rmdir(2)。
这里rmdir(2)
是对文档的隐式引用rmdir() system call
。这里的数字 (2) 是旧的 unix 手册页约定,表示手册页的第 2 节,包含内核接口。
Node.js v12.10.0 将recursive
选项引入fs.rmdir
. 由于fs.mkdir
从 v10.12.0 开始支持相同的选项,因此可以递归地执行创建和删除目录。
$ 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
这对我有用
fs.rmdirSync(folderpath, {recursive: true});
2021年编辑:
现在它似乎已在 v14 中被替换为:
fs.rmSync('./output', {recursive: true, force: true});
只是这一堆答案中的一个小点,但我认为指出这一点很好。
就个人而言(并且通常),我更愿意使用已经存在的库(如果有可用的库)来完成任务。对我来说,尤其是在开源世界中,采用一个已经存在的东西意味着使用和改进一个已经存在的东西,这最终可能比我自己做更好的结果(我正在改进其他人拥有的东西)完毕)。
在这种情况下,通过一个小的搜索,我发现了模块 fs-extra,它旨在替代rimraf
并解决递归删除目录的需要(显然是异步和同步版本)。此外,它在 github 上获得了很多星,并且目前似乎得到了维护:这两个条件,除了可以满足需求的事实外,对我来说也是可行的(几乎是一段时间)。
使用 child_process.execFile更快。
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 上不起作用
从节点 v16 开始,recursivefs.rmdir
现在已弃用。替换是fs.rm
.
与承诺一起使用:
const fs = require("fs/promises")
(async () => {
await fs.rm("directory", { recursive: true })
})()
传统的:
const fs = require("fs")
fs.rm("directory", { recursive: true }, (err) => {
// Callback
})
该force
选项也与此处提及有关,因为如果文件夹丢失,它将防止该方法引发错误,这对于清理临时文件很有用。
这是一个适用于 Promise 的异步递归版本。我使用“Q”库,但任何人都会做一些更改(例如“失败”功能)。
为了使用它,我们必须对一些核心 Node 函数进行一些简单的包装,即 fs.stat、fs.readdir、fs.unlink 和 fs.rmdir,以使它们对 Promise 友好。
他们来了:
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;
}
所以这里是递归 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() 函数将删除名称由路径给出的目录。仅当目录为空目录时才应删除该目录。
如果目录不是空目录,rmdir() 将失败并将 errno 设置为 [EEXIST] 或 [ENOTEMPTY]。
我意识到这并不能完全回答手头的问题,但我认为这可能对将来在这里搜索的人有用(对我来说是这样!):我做了一个小片段,允许递归删除空目录。如果一个目录(或它的任何后代目录)里面有内容,它就会被单独留下:
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/yoavniran/adbbe12ddf7978e070c0
此函数将递归地同步删除您指定的目录或文件:
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);
}
}
如果出现以下情况,我尚未测试此函数的行为:
Node.js 的递归删除目录
事实证明,Node.js fs 模块没有递归删除目录及其内容的方法。相反,您应该检查目录结构并删除原子项目,即单个文件和空目录。所以我在https://gist.github.com/2367067找到了一个用 JavaScript 制作的 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创建的咖啡脚本原型函数,它递归删除文件夹
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()
这是测试:
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。(快点)
/**
* 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,并且只调用一次回调(成功或遇到第一个错误)。用上面的替换这个 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并且应该是可移植的。
这篇文章得到了谷歌的最佳答案,但没有一个答案给出了解决方案:
不使用同步功能
不需要外部库
不直接使用 bash
这是我的async
解决方案,它不假设安装节点以外的任何其他内容:
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);
});
}); } }
遵循@geedew 的回答。
这是一个异步实现rm -r
(即您可以将路径传递给文件或目录)。我不是经验丰富的 nodejs 开发人员,我感谢任何建议或建设性的批评。
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(); // doesn't 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();
});
}
});
};
像这样调用:
rmRasync('/path/to/some/file/or/dir', function (err) {
if (err) return console.error('Could not rm', err);
// else success
});
这里令人惊讶的冗长和糟糕的答案......
要在大多数系统上删除非空目录:
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
});
这适用于 MacOS 和 Linux,但可能不适用于某些 Windows 操作系统。