删除非空目录

在我的Node应用程序中,我需要删除一个有一些文件的目录,但fs.rmdir只适用于空目录。我该怎么做呢?

445696 次浏览

有一个名为rimraf (https://npmjs.org/package/rimraf)的模块。它提供了与rm -Rf相同的功能

异步用法:

var rimraf = require("rimraf");
rimraf("/some/directory", function () { console.log("done"); });

同步用法:

rimraf.sync("/some/directory");

我写了这个函数叫remove folder。它将递归地删除一个位置中的所有文件和文件夹。它唯一需要的包是异步的。

var async = require('async');


function removeFolder(location, next) {
fs.readdir(location, function (err, files) {
async.each(files, function (file, cb) {
file = location + '/' + file
fs.stat(file, function (err, stat) {
if (err) {
return cb(err);
}
if (stat.isDirectory()) {
removeFolder(file, cb);
} else {
fs.unlink(file, function (err) {
if (err) {
return cb(err);
}
return cb();
})
}
})
}, function (err) {
if (err) return next(err)
fs.rmdir(location, function (err) {
return next(err)
})
})
})
}

同步删除文件夹

    const fs = require('fs');
const Path = require('path');


const deleteFolderRecursive = function (directoryPath) {
if (fs.existsSync(directoryPath)) {
fs.readdirSync(directoryPath).forEach((file, index) => {
const curPath = path.join(directoryPath, file);
if (fs.lstatSync(curPath).isDirectory()) {
// recurse
deleteFolderRecursive(curPath);
} else {
// delete file
fs.unlinkSync(curPath);
}
});
fs.rmdirSync(directoryPath);
}
};

只要使用删除目录模块!这很简单。

我到达这里,同时试图通过gulp,我正在为进一步的到达而写。

当你想要使用del删除文件和文件夹时,你应该追加/**进行递归删除。

gulp.task('clean', function () {
return del(['some/path/to/delete/**']);
});

大多数在Node.js中使用fs的人都喜欢函数接近“Unix方式”处理文件。我使用fs-extra来带来所有很酷的东西:

fs-extra包含没有包含在普通Node.js中的方法 fs包。例如mkdir -p、cp -r、rm -rf

更好的是,fs-extra是对本地fs的替换。fs中的所有方法都是未修改的,并附加到它。 这意味着你可以用fs-extra替换fs:

// this can be replaced
const fs = require('fs')


// by this
const fs = require('fs-extra')

然后你可以这样删除一个文件夹:

fs.removeSync('/tmp/myFolder');
//or
fs.remove('/tmp/myFolder', callback);

我希望有一种方法可以做到这一点,而不需要为如此微小和常见的东西添加额外的模块,但这是我能想到的最好的方法。

< p >更新: 现在应该在Windows上工作(测试Windows 10),也应该在Linux/Unix/BSD/Mac系统上工作
const
execSync = require("child_process").execSync,
fs = require("fs"),
os = require("os");


let removeDirCmd, theDir;


removeDirCmd = os.platform() === 'win32' ? "rmdir /s /q " : "rm -rf ";


theDir = __dirname + "/../web-ui/css/";


// WARNING: Do not specify a single file as the windows rmdir command will error.
if (fs.existsSync(theDir)) {
console.log(' removing the ' + theDir + ' directory.');
execSync(removeDirCmd + '"' + theDir + '"', function (err) {
console.log(err);
});
}

下面是@SharpCoder的回答的异步版本

const fs = require('fs');
const path = require('path');


function deleteFile(dir, file) {
return new Promise(function (resolve, reject) {
var filePath = path.join(dir, file);
fs.lstat(filePath, function (err, stats) {
if (err) {
return reject(err);
}
if (stats.isDirectory()) {
resolve(deleteDirectory(filePath));
} else {
fs.unlink(filePath, function (err) {
if (err) {
return reject(err);
}
resolve();
});
}
});
});
};


function deleteDirectory(dir) {
return new Promise(function (resolve, reject) {
fs.access(dir, function (err) {
if (err) {
return reject(err);
}
fs.readdir(dir, function (err, files) {
if (err) {
return reject(err);
}
Promise.all(files.map(function (file) {
return deleteFile(dir, file);
})).then(function () {
fs.rmdir(dir, function (err) {
if (err) {
return reject(err);
}
resolve();
});
}).catch(reject);
});
});
});
};

我修改的答案从@oconnecp (https://stackoverflow.com/a/25069828/3027390)

< p >使用路径。加入可以获得更好的跨平台体验。 所以,不要忘记要求它。

var path = require('path');

也将函数重命名为rimraf;)

/**
* Remove directory recursively
* @param {string} dir_path
* @see https://stackoverflow.com/a/42505874/3027390
*/
function rimraf(dir_path) {
if (fs.existsSync(dir_path)) {
fs.readdirSync(dir_path).forEach(function(entry) {
var entry_path = path.join(dir_path, entry);
if (fs.lstatSync(entry_path).isDirectory()) {
rimraf(entry_path);
} else {
fs.unlinkSync(entry_path);
}
});
fs.rmdirSync(dir_path);
}
}

另一种替代方法是使用fs-promise模块,它提供了fs-extra模块的承诺版本

你可以这样写:

const { remove, mkdirp, writeFile, readFile } = require('fs-promise')
const { join, dirname } = require('path')


async function createAndRemove() {
const content = 'Hello World!'
const root = join(__dirname, 'foo')
const file = join(root, 'bar', 'baz', 'hello.txt')


await mkdirp(dirname(file))
await writeFile(file, content)
console.log(await readFile(file, 'utf-8'))
await remove(join(__dirname, 'foo'))
}


createAndRemove().catch(console.error)

注意:async/await需要最新的nodejs版本(7.6+)

事实上的包是rimraf,但这里是我的小异步版本:

const fs = require('fs')
const path = require('path')
const Q = require('q')


function rmdir (dir) {
return Q.nfcall(fs.access, dir, fs.constants.W_OK)
.then(() => {
return Q.nfcall(fs.readdir, dir)
.then(files => files.reduce((pre, f) => pre.then(() => {
var sub = path.join(dir, f)
return Q.nfcall(fs.lstat, sub).then(stat => {
if (stat.isDirectory()) return rmdir(sub)
return Q.nfcall(fs.unlink, sub)
})
}), Q()))
})
.then(() => Q.nfcall(fs.rmdir, dir))
}


一个快速而肮脏的方法(可能是为了测试)可能是直接使用execspawn方法调用OS调用来删除目录。阅读更多关于NodeJs child_process的内容。

let exec = require('child_process').exec
exec('rm -Rf /tmp/*.zip', callback)

缺点是:

  1. 你依赖于底层的操作系统,即相同的方法可以在unix/linux中运行,但可能不能在windows中运行。
  2. 您不能根据条件或错误劫持流程。您只需将任务交给底层操作系统,并等待退出码返回。

好处:

  1. 这些流程可以异步运行。
  2. 可以监听命令的输出/错误,因此命令输出不会丢失。如果操作未完成,请检查错误码后重试。

如果你正在使用的节点8+想要异步,不想要外部依赖,这里是async/await版本:

const path = require('path');
const fs = require('fs');
const util = require('util');


const readdir = util.promisify(fs.readdir);
const lstat = util.promisify(fs.lstat);
const unlink = util.promisify(fs.unlink);
const rmdir = util.promisify(fs.rmdir);


const removeDir = async (dir) => {
try {
const files = await readdir(dir);
await Promise.all(files.map(async (file) => {
try {
const p = path.join(dir, file);
const stat = await lstat(p);
if (stat.isDirectory()) {
await removeDir(p);
} else {
await unlink(p);
console.log(`Removed file ${p}`);
}
} catch (err) {
console.error(err);
}
}))
await rmdir(dir);
console.log(`Removed dir ${dir}`);
} catch (err) {
console.error(err);
}
}

使用fs.promises实现@SharpCoder的回答的异步版本:

const fs = require('fs');
const afs = fs.promises;


const deleteFolderRecursive = async path =>  {
if (fs.existsSync(path)) {
for (let entry of await afs.readdir(path)) {
const curPath = path + "/" + entry;
if ((await afs.lstat(curPath)).isDirectory())
await deleteFolderRecursive(curPath);
else await afs.unlink(curPath);
}
await afs.rmdir(path);
}
};
const fs = require("fs")
const path = require("path")


let _dirloc = '<path_do_the_directory>'


if (fs.existsSync(_dirloc)) {
fs.readdir(path, (err, files) => {
if (!err) {
for (let file of files) {
// Delete each file
fs.unlinkSync(path.join(_dirloc, file))
}
}
})
// After the 'done' of each file delete,
// Delete the directory itself.
if (fs.unlinkSync(_dirloc)) {
console.log('Directory has been deleted!')
}
}

这是一种使用promisify和两个帮助函数(to和toAll)来解决承诺的方法。

它以异步方式执行所有操作。

const fs = require('fs');
const { promisify } = require('util');
const to = require('./to');
const toAll = require('./toAll');


const readDirAsync = promisify(fs.readdir);
const rmDirAsync = promisify(fs.rmdir);
const unlinkAsync = promisify(fs.unlink);


/**
* @author Aécio Levy
* @function removeDirWithFiles
* @usage: remove dir with files
* @param {String} path
*/
const removeDirWithFiles = async path => {
try {
const file = readDirAsync(path);
const [error, files] = await to(file);
if (error) {
throw new Error(error)
}
const arrayUnlink = files.map((fileName) => {
return unlinkAsync(`${path}/${fileName}`);
});
const [errorUnlink, filesUnlink] = await toAll(arrayUnlink);
if (errorUnlink) {
throw new Error(errorUnlink);
}
const deleteDir = rmDirAsync(path);
const [errorDelete, result] = await to(deleteDir);
if (errorDelete) {
throw new Error(errorDelete);
}
} catch (err) {
console.log(err)
}
};

我通常不复活旧线程,但这里有一个很多,没有rimraf答案,这些对我来说都太复杂了。

首先,在现代Node (>= v8.0.0)中,你可以只使用节点核心模块来简化过程,完全异步,并在5行函数中并行化文件的解链接,并且仍然保持可读性:

const fs = require('fs');
const path = require('path');
const { promisify } = require('util');
const readdir = promisify(fs.readdir);
const rmdir = promisify(fs.rmdir);
const unlink = promisify(fs.unlink);


exports.rmdirs = async function rmdirs(dir) {
let entries = await readdir(dir, { withFileTypes: true });
await Promise.all(entries.map(entry => {
let fullPath = path.join(dir, entry.name);
return entry.isDirectory() ? rmdirs(fullPath) : unlink(fullPath);
}));
await rmdir(dir);
};

另一方面,路径遍历攻击的保护不适合此函数,因为

  1. 基于单一责任原则,它超出了范围。
  2. 应该由调用者处理此函数。这类似于命令行rm -rf,因为它接受一个参数,并允许用户在被要求时rm -rf /。脚本的职责不是保护rm程序本身。
  3. 这个函数将无法确定这样的攻击,因为它没有一个参照系。同样,这是调用者的责任,调用者将拥有意图的上下文,为它提供一个比较路径遍历的引用。
  4. 符号链接不需要考虑,因为.isDirectory()是符号链接的false,并且被解链接而不是递归。

最后但并非最不重要的是,有一个罕见的竞争条件,如果在这个递归运行时,其中一个条目在正确的时间被取消链接或删除这个脚本,递归可能会出错。由于这种情况在大多数环境中并不典型,因此可能会被忽略。然而,如果需要(对于一些边缘情况),这个问题可以通过下面这个稍微复杂一点的例子来缓解:

exports.rmdirs = async function rmdirs(dir) {
let entries = await readdir(dir, { withFileTypes: true });
let results = await Promise.all(entries.map(entry => {
let fullPath = path.join(dir, entry.name);
let task = entry.isDirectory() ? rmdirs(fullPath) : unlink(fullPath);
return task.catch(error => ({ error }));
}));
results.forEach(result => {
// Ignore missing files/directories; bail on other errors
if (result && result.error.code !== 'ENOENT') throw result.error;
});
await rmdir(dir);
};

编辑:使isDirectory()成为一个函数。最后删除实际目录。修复丢失的递归。

超速度和防故障

你可以使用lignator包(https://www.npmjs.com/package/lignator),它比任何异步代码(例如rimraf)都快,而且更防故障(特别是在Windows中,文件删除不是瞬时的,文件可能被其他进程锁定)。

4,36 GB数据,28042个文件,4217个Windows文件夹在15秒内删除,而旧硬盘上的rimraf的60秒

const lignator = require('lignator');


lignator.remove('./build/');
< p > 同步文件夹删除文件或仅一个文件。 < br > < br > 我不是一个很好的给予者,也不是一个贡献者,但我找不到一个很好的解决这个问题的方法,我必须找到我的方法……所以我希望你会喜欢它:)

适用于任何数量的嵌套目录和子目录。注意递归函数时'this'的作用域,您的实现可能不同。在我的例子中,这个函数保留在另一个函数的返回中这就是为什么我用这个来调用它。
< br >

    const fs = require('fs');


deleteFileOrDir(path, pathTemp = false){
if (fs.existsSync(path)) {
if (fs.lstatSync(path).isDirectory()) {
var files = fs.readdirSync(path);
if (!files.length) return fs.rmdirSync(path);
for (var file in files) {
var currentPath = path + "/" + files[file];
if (!fs.existsSync(currentPath)) continue;
if (fs.lstatSync(currentPath).isFile()) {
fs.unlinkSync(currentPath);
continue;
}
if (fs.lstatSync(currentPath).isDirectory() && !fs.readdirSync(currentPath).length) {
fs.rmdirSync(currentPath);
} else {
this.deleteFileOrDir(currentPath, path);
}
}
this.deleteFileOrDir(path);
} else {
fs.unlinkSync(path);
}
}
if (pathTemp) this.deleteFileOrDir(pathTemp);
}

//不使用任何第三方lib

const fs = require('fs');
var FOLDER_PATH = "./dirname";
var files = fs.readdirSync(FOLDER_PATH);
files.forEach(element => {
fs.unlinkSync(FOLDER_PATH + "/" + element);
});
fs.rmdirSync(FOLDER_PATH);

在Node.js的最新版本(12.10.0或更高版本)中,rmdir样式函数fs.rmdir()fs.rmdirSync()fs.promises.rmdir()有一个新的实验性选项recursive,允许删除非空目录,例如:

fs.rmdir(path, { recursive: true });

GitHub上的相关PR: https://github.com/nodejs/node/pull/29168

从Node.js 14.14.0开始,推荐的方法是使用fs.rmSync:

fs.rmSync(dir, { recursive: true, force: true });

recursivefs.rmdir的实验性选项

function rm (path, cb) {
fs.stat(path, function (err, stats) {
if (err)
return cb(err);


if (stats.isFile())
return fs.unlink(path, cb);


fs.rmdir(path, function (err) {
if (!err || err && err.code != 'ENOTEMPTY')
return cb(err);


fs.readdir(path, function (err, files) {
if (err)
return cb(err);


let next = i => i == files.length ?
rm(path, cb) :
rm(path + '/' + files[i], err => err ? cb(err) : next(i + 1));


next(0);
});
});
});
}

2020年更新

从12.10.0版本开始,recursiveOption已添加为选项。

注意递归删除是实验性的.;

所以你会做同步:

fs.rmdirSync(dir, {recursive: true});

或者async:

fs.rmdir(dir, {recursive: true});

根据fs文档fsPromises目前在实验的基础上提供了recursive选项,至少在我自己的Windows情况下,它删除了目录和其中的任何文件。

fsPromises.rmdir(path, {
recursive: true
})

recursive: true是否删除Linux和MacOS上的文件?

❄️你可以使用graph-fs

directory.delete()
const fs = require("fs");
fs.rmdir("./test", { recursive: true }, (err) => {
if (err) {
console.error(err);
}
});


提供recursive: true选项。并且它将递归地删除给定路径下的所有文件和目录。(假设test是根目录。)

return new Promise((resolve, reject) => {
const fs = require("fs");
// directory path
const dir = "your/dir";


// delete directory recursively <------
fs.rmdir(dir, { recursive: true }, (err) => {
if (err) {
reject(err);
}
resolve(`${dir} is deleted!`);
});
});

2020的答案

如果你想在npm脚本中完成它,如果你使用命令npx,你不需要预先安装任何第三方包

例如,如果你想在运行npm run clean时删除文件夹经销.cache,那么只需将此命令添加到你的package.json

{
"scripts": {
"clean": "npx rimraf dist .cache"
}
}

它适用于任何操作系统

[编辑:使用node.js v15.5.0]

刚刚尝试使用这里发布的一些解决方案后,我遇到了以下弃用警告:

(node:13202) [DEP0147] DeprecationWarning:在未来的版本 node . js, fs。Rmdir (path, {recursive: true})将抛出 不存在或者是一个文件。使用fs。Rm (path, {recursive: true, force: true })而不是< / p >

fs.rm(path, { recursive: true, force: true });工作得很好,如果你想使用阻塞版本,可以使用fs.rmSync(path, { recursive: true, force: true });

截至节点v14(2020年10月),fs模块具有fs.rmrs.rmSync,它们支持递归的非空目录解链接:

https://nodejs.org/docs/latest-v14.x/api/fs.html#fs_fs_rm_path_options_callback

所以你现在可以这样做:

const fs = require('fs');
fs.rm('/path/to/delete', { recursive: true }, () => console.log('done'));

或者:

const fs = require('fs');
fs.rmSync('/path/to/delete', { recursive: true });
console.log('done');

解释

从Node.js v14开始,我们现在可以使用require("fs").promises.rm函数使用承诺删除文件。第一个参数是要删除的文件或文件夹(即使是不存在的文件或文件夹)。你可以在第二个参数的对象中使用recursiveforce选项,用-rf选项来模拟rm Shell命令实用程序的行为。

例子

"use strict";


require("fs").promises.rm("directory", {recursive: true, force: true}).then(() => {
console.log("removed");
}).catch(error => {
console.error(error.message);
});

看到

Node.js v14文档 .js v14文档

Mozilla Developer Promises Documentation .

rm命令手册页 . rref ="https://man7.org/linux/man-pages/man1/rm.1.html" rel="noreferrer">rm命令手册页

在Node文档中,可以看到在这里

要获得类似于rm -rf Unix命令的行为,请使用带有{ recursive: true, force: true }选项的fs.rm()

例如(ESM)

import { rm } from 'node:fs/promises';


await rm('/path/to', { recursive: true, force: true });

方法删除非空目录

rmdir(path,{recursive:true,force:true}
rm(path,{recursive:true,force:true}

将工作

代码片段:

const fsp = require("fs/promises");


deleteDirRecursively("./b");
removeRecursively("./BCD/b+");


async function deleteDirRecursively(dirPath) {
try {
// fsPromises.rmdir() on a file (not a directory) results in the promise being rejected
// with an ENOENT error on Windows and an ENOTDIR error on POSIX.
// To get a behavior similar to the rm -rf Unix command,
// use fsPromises.rm() with options { recursive: true, force: true }.
//will not thorw error if dir is empty
//will thow error if dir is not present
await fsp.rmdir(dirPath, { recursive: true, force: true });
console.log(dirPath, "deleted successfully");
} catch (err) {
console.log(err);
}


async function removeRecursively(path) {
try {
//has ability to remove both file and dir
//can delete dir recursively and forcefully
//will delete an empty dir.
//will remove all the contents of a dir.
// the only difference between rmdir and rm is that rmdir can only delete dir's
await fsp.rm(path, { recursive: true, force: true });
console.log(path, "deleted successfully");
} catch (err) {
console.log(err);
}
}

如果你更喜欢async/await,你可以使用fs/promises API。

const fs = require('fs/promises');


const removeDir = async (dirPath) => {
await fs.rm(dirPath, {recursive: true});
}

如果您知道文件夹中单个文件的路径,并希望删除包含该文件的文件夹。

const fs = require('fs/promises');
const path = require('path');


const removeDir = async (filePath) => {
const { dir } = path.parse(filePath);
await fs.rm(dir, { recursive: true });
}