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


当前回答

解释

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

例子

"use strict";

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

See

Node.js v14文档

Mozilla开发者承诺文档

Rm命令手册

其他回答

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

异步用法:

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

同步用法:

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

而递归是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);
            });
        });
    });
}

我通常不复活旧线程,但这里有很多关于搅动和没有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);
};

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

It is out of scope based on the Single Responsibility Principle. Should be handled by the caller not this function. This is akin to the command-line rm -rf in that it takes an argument and will allow the user to rm -rf / if asked to. It would be the responsibility of a script to guard not the rm program itself. This function would be unable to determine such an attack since it does not have a frame of reference. Again that is the responsibility of the caller who would have the context of intent which would provide it a reference to compare the path traversal. Sym-links are not a concern as .isDirectory() is false for sym-links and are unlinked not recursed into.

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

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()成为一个函数。最后删除实际目录。修复丢失的递归。

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

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

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

@SharpCoder的答案使用fs.promises的异步版本:

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