在我的 Node 应用程序中,我需要删除一个包含一些文件但fs.rmdir
仅适用于空目录的目录。我怎样才能做到这一点?
33 回答
从 Node.js 14.14.0 开始,推荐的方法是使用fs.rmSync
:
fs.rmSync(dir, { recursive: true, force: true });
有一个名为rimraf
( https://npmjs.org/package/rimraf ) 的模块。它提供与rm -Rf
异步用法:
var rimraf = require("rimraf");
rimraf("/some/directory", function () { console.log("done"); });
同步使用:
rimraf.sync("/some/directory");
同步删除文件夹
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);
}
};
大多数使用fs
Node.js 的人都希望使用接近“Unix 方式”处理文件的函数。我正在使用fs-extra带来所有很酷的东西:
fs-extra 包含未包含在 vanilla Node.js fs 包中的方法。如 mkdir -p、cp -r 和 rm -rf。
更好的是,fs-extra是原生 fs 的替代品。fs 中的所有方法都未修改并附加到它。这意味着您可以将 fs 替换为fs-extra:
// 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);
从 Node v14(2020 年 10 月)开始,该fs
模块具有fs.rm
并且rs.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');
我修改后的答案来自@oconnecp ( https://stackoverflow.com/a/25069828/3027390 )
使用 path.join 以获得更好的跨平台体验。所以,不要忘记要求它。
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);
}
}
我通常不会复活旧线程,但是这里有很多流失并且没有 rimraf 答案,这些对我来说似乎都过于复杂。
首先在现代 Node (>= v8.0.0) 中,您可以仅使用节点核心模块来简化流程,完全异步,并在五行函数中同时并行化文件的取消链接,并且仍然保持可读性:
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);
};
另一方面,路径遍历攻击的防护不适合此功能,因为
- 它超出了单一职责原则的范围。
- 应该由调用者而不是这个函数来处理。这类似于命令行
rm -rf
,因为它需要一个参数,并允许用户在rm -rf /
被要求时使用。脚本的职责不是保护rm
程序本身。 - 此函数将无法确定此类攻击,因为它没有参考框架。这也是调用者的责任,调用者将拥有意图的上下文,这将为它提供一个参考来比较路径遍历。
.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()
一个功能。最后删除实际目录。修复丢失的递归。
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);
});
});
});
};
我写了这个叫做删除文件夹的函数。它将递归删除某个位置的所有文件和文件夹。它唯一需要的包是异步的。
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)
})
})
})
}
[编辑:使用 node.js v15.5.0]
刚刚尝试使用此处发布的一些解决方案,我遇到了以下弃用警告:
(node:13202) [DEP0147] DeprecationWarning:在 Node.js 的未来版本中,如果 path 不存在或者是文件,fs.rmdir(path, { recursive: true }) 将抛出。改用 fs.rm(path, { recursive: true, force: true })
fs.rm(path, { recursive: true, force: true });
fs.rmSync(path, { recursive: true, force: true });
如果你想使用阻塞版本,效果很好。
如果您使用的是 node 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);
}
}
const fs = require("fs");
fs.rmdir("./test", { recursive: true }, (err) => {
if (err) {
console.error(err);
}
});
提供recursive: true
选项。它将递归删除给定路径的所有文件和目录。(假设test
目录存在于根目录。)
我在试图克服困难时到达这里,gulp
我正在写信以求更进一步。
gulp-clean
已弃用gulp-rimraf
gulp-rimraf
不赞成使用delete-files-folders
当您想使用 删除文件和文件夹del
时,您应该追加/**
以进行递归删除。
gulp.task('clean', function () {
return del(['some/path/to/delete/**']);
});
使用 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);
}
};
在最新版本的 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
根据fs
文档,fsPromises
目前recursive
在实验基础上提供了该选项,至少在我自己的 Windows 上,它会删除目录和其中的任何文件。
fsPromises.rmdir(path, {
recursive: true
})
是否recursive: true
删除 Linux 和 MacOS 上的文件?
解释
从 Node.js v14 开始,我们现在可以使用该require("fs").promises.rm
函数通过 Promise 删除文件。第一个参数将是要删除的文件或文件夹(甚至是不存在的文件或文件夹)。您可以在第二个参数的对象中使用recursive
和force
选项来模拟带有选项的rm
Shell 命令实用程序的行为。-rf
例子
"use strict";
require("fs").promises.rm("directory", {recursive: true, force: true}).then(() => {
console.log("removed");
}).catch(error => {
console.error(error.message);
});
看
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!`);
});
});
如此处所写。
要获得类似于
rm -rf
Unix 命令的行为,请使用fs.rm()
with options{ recursive: true, force: true }
。
只需使用rmdir 模块!这很容易和简单。
事实上的包是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))
}
一种快速而肮脏的方法(可能用于测试)可能是直接使用exec
orspawn
方法来调用操作系统调用以删除目录。阅读更多关于NodeJs child_process的信息。
let exec = require('child_process').exec
exec('rm -Rf /tmp/*.zip', callback)
缺点是:
- 您取决于底层操作系统,即相同的方法将在 unix/linux 中运行,但可能不在 Windows 中。
- 您不能根据条件或错误劫持流程。您只需将任务交给底层操作系统并等待退出代码返回。
好处:
- 这些进程可以异步运行。
- 您可以监听命令的输出/错误,因此命令输出不会丢失。如果操作未完成,您可以检查错误代码并重试。
2020 更新
从 12.10.0 版本开始,为选项添加了recursiveOption 。
请注意,递归删除是实验性的。
所以你会做同步:
fs.rmdirSync(dir, {recursive: true});
或异步:
fs.rmdir(dir, {recursive: true});
超高速和防故障
您可以使用该lignator
软件包(https://www.npmjs.com/package/lignator),它比任何异步代码(例如 rimraf)都更快,并且更防故障(尤其是在 Windows 中,文件删除不是即时的,文件可能被其他进程锁定)。
Windows 上 4,36 GB 数据、28 042 个文件、4 217 个文件夹在15 秒内删除,而 rimraf在旧硬盘上则需要 60 秒。
const lignator = require('lignator');
lignator.remove('./build/');
将文件夹与文件同步删除或仅删除一个文件。
我既不是给予者也不是贡献者,但我找不到解决这个问题的好方法,我必须找到自己的方法......所以我希望你会喜欢它:)
非常适合我使用任何数量的嵌套目录和子目录。递归函数时注意“this”的范围,您的实现可能会有所不同。在我的情况下,这个函数停留在另一个函数的返回中,这就是我用这个调用它的原因。
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);
}
2020答案
如果您想在npm 脚本中执行此操作,如果您使用该命令,则无需预先安装任何 3rd 方包npx
例如,如果您想在运行时删除文件夹dist和.cachenpm run clean
,那么只需将此命令添加到您的package.json
{
"scripts": {
"clean": "npx rimraf dist .cache"
}
}
它适用于任何操作系统
我希望有一种方法可以做到这一点,而无需额外的模块来处理如此微小和常见的事情,但这是我能想到的最好的方法。
更新:现在应该可以在 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);
});
}
另一种选择是使用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+)
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)来解决 promise 的方法。
它异步执行所有操作。
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)
}
};
//不使用任何第三方库
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);
Whilerecursive
是一个实验选项fs.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);
});
});
});
}
❄️你可以使用graph-fs
directory.delete()