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


当前回答

我通常不复活旧线程,但这里有很多关于搅动和没有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()成为一个函数。最后删除实际目录。修复丢失的递归。

其他回答

我写了这个函数叫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)
            })
        })
    })
}

2020的答案

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

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

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

它适用于任何操作系统

只需使用rmdir模块!这很简单。

在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

如果你正在使用的节点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);
    }
}