删除非空的目录

sac*_*hin 250 filesystems node.js

在我的Node应用程序中,我需要删除一个包含一些文件的目录,但fs.rmdir只适用于空目录.我怎样才能做到这一点?

Mor*_*len 278

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

异步使用:

var rimraf = require("rimraf");
rimraf("/some/directory", function () { console.log("done"); });
Run Code Online (Sandbox Code Playgroud)

同步使用:

rimraf.sync("/some/directory");
Run Code Online (Sandbox Code Playgroud)

  • 这可以通过NodeJS Core lib轻松完成,为什么要安装一个不受维护的第三方软件包呢? (20认同)
  • "但即使功能低于它的优势,也可以在系统中添加不需要的软件包." 我非常不同意.你完全没有任何理由重新启动第19百万次轮子,并冒着在这个过程中引入漏洞或安全漏洞的风险.至少这是浪费时间.Inb4"如果他们放弃包裹怎么办":在极少数情况下,从npm注册表中删除包,你总是可以用你自己的_then._替换它.在打破它之前包扎你的头是没有意义的. (13认同)
  • 您现在可以使用“递归”选项:/sf/answers/4050631581/ (6认同)
  • @EmettSpeer你什么意思"轻松完成"?在下面的回答中自编写一个像`deleteFolderRecursive`这样的函数? (4认同)
  • 现在您可以使用 del (https://www.npmjs.com/package/del) 而不是 @rimraf (3认同)

Sha*_*der 194

同步删除文件夹

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

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

  • 可能想添加一些你不会在'/'上意外运行的检查.例如,传递空路径和文件中的拼写错误可能导致curPath成为根目录. (26认同)
  • Windows有\ slash,所以`path.join(dirpath,file)`应该比`path +"/"+ file`更好 (9认同)
  • 更强大的实现:将`var curPath = path +"/"+ file;`替换为`var curPath = p.join(path,file);`如果包含路径模块:`var p = require("path") ` (8认同)
  • 由于在一个滴答时间内操作过多,您可能会因此代码获得"超出最大调用堆栈大小".@Walf如果你运行控制台应用程序,你有1个客户端,而不是更多.因此,在这种情况下,无需使用async for console app (4认同)
  • 我收到'错误:ENOTEMPTY:目录不为空' (4认同)

Pie*_*oui 156

大多数使用fsNode.js 的人都希望函数接近处理文件的"Unix方式".我正在使用fs-extra带来所有很酷的东西:

fs-extra包含未包含在vanilla 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')
Run Code Online (Sandbox Code Playgroud)

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

fs.removeSync('/tmp/myFolder'); 
//or
fs.remove('/tmp/myFolder', callback);
Run Code Online (Sandbox Code Playgroud)


小智 37

截至2019年...

使用Node.js 12.10,您最终可以做一个简单的事情:

fs.rmdir(dir, { recursive: true });
Run Code Online (Sandbox Code Playgroud)

recursive选项以递归方式删除整个内容。

  • 函数签名实际上是“fs.rmdir(path[, options],callback)”或“fs.rmdirSync(path[, options])” (6认同)
  • 从节点v13.0.1开始,递归删除仍处于试验阶段 (5认同)
  • 递归 rmdir 在内部使用 rimraf,如拉取请求 https://github.com/nodejs/node/pull/29168/files 中所示 (4认同)
  • @Emerica 在官方 [node.js 文档](https://nodejs.org/api/fs.html#fs_fs_rmdir_path_options_callback) 中,有一个大的橙色通知说 `fs.rmdir` 是稳定性 1 的实验。“稳定性:1 - 实验性的。该功能不受语义版本控制规则的约束。在未来的任何版本中可能会发生非向后兼容的更改或删除。不建议在生产环境中使用该功能。 (3认同)
  • 请注意,自 [v14.14.0](https://nodejs.org/en/blog/release/) 起,“recursive”选项[已弃用](https://github.com/nodejs/node/pull/35579) v14.14.0/)。API 文档建议使用 `fs.rm(path, { recursive: true, force: true })` 代替。 (3认同)
  • 如果子目录包含文件,则这与“ rm -rf”不同,那么您将得到错误:ENOTEMPTY:目录不为空 (2认同)
  • @anneb如果使用的是较旧版本的Node.js(<12.10),则会发生这种情况。最新版本可识别“递归:true”选项,并删除非空文件夹而不会引起投诉。 (2认同)

小智 27

为节点s14的(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'));
Run Code Online (Sandbox Code Playgroud)

或者:

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


thy*_*bzi 21

我从@oconnecp修改了答案(/sf/answers/1754887991/)

使用path.join可获得更好的跨平台体验.所以,不要忘记要求它.

var path = require('path');
Run Code Online (Sandbox Code Playgroud)

还将功能重命名为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);
    }
}
Run Code Online (Sandbox Code Playgroud)


mdm*_*ndo 18

从 Node 文档中可以看到这里

要获得类似于 Unix 命令的行为rm -rf,请fs.rm()与 options 一起使用{ recursive: true, force: true }

例如

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

await rm('/path/to', { recursive: true, force: true });
Run Code Online (Sandbox Code Playgroud)


Suk*_*ima 14

我通常不复活旧线程,但是这里有很多搅动,并且没有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);
};
Run Code Online (Sandbox Code Playgroud)

另一方面,对于路径遍历攻击的防护措施不适用于此功能,因为

  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);
};
Run Code Online (Sandbox Code Playgroud)

编辑:isDirectory()一个功能。最后删除实际目录。修复缺少的递归。


Ton*_*rix 11

这是@ 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);
            });
        });
    });
};
Run Code Online (Sandbox Code Playgroud)


oco*_*ecp 10

我写了这个函数叫做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)
            })
        })
    })
}
Run Code Online (Sandbox Code Playgroud)

  • 是的,编写自己的代码是非常糟糕的,因为使用数十个第三方模块进行相对简单的操作从未证明在大规模应用程序中有任何缺点. (85认同)
  • 这个想法实际上是不写自己的代码,如果它已经由其他人编写.更好的方法是使用rimraf或fs-extra或任何其他节点模块,为您完成工作. (4认同)

Ron*_*onZ 8

如果您正在使用节点8+想要asyncronicity并且不希望外部依赖,那么这里是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);
    }
}
Run Code Online (Sandbox Code Playgroud)


the*_*uff 8

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

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

(node:13202) [DEP0147] 弃用警告:在 Node.js 的未来版本中,如果路径不存在或者是一个文件,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 });如果你想使用阻塞版本,效果很好。


Fai*_*izi 7

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

Run Code Online (Sandbox Code Playgroud)

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


Gio*_*uno 5

2020年更新

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

请注意,递归删除是实验性的

所以你会做同步:

fs.rmdirSync(dir, {recursive: true});
Run Code Online (Sandbox Code Playgroud)

或者对于异步:

fs.rmdir(dir, {recursive: true});
Run Code Online (Sandbox Code Playgroud)


Pri*_*Nom 5

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

fsPromises.rmdir(path, {
  recursive: true
})
Run Code Online (Sandbox Code Playgroud)

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


Ami*_*IRI 5

说明

从 Node.js v14 开始,我们现在可以使用该require("fs").promises.rm函数通过 Promise 删除文件。第一个参数是要删除的文件或文件夹(甚至是不存在的文件或文件夹)。您可以在第二个参数的对象中使用recursive和选项来模仿Shell 命令实用程序force的行为rm-rf

例子

"use strict";

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

Node.js v14 文档

Mozilla 开发者承诺文档

rm命令手册页


Ami*_*wal 5

如果您更喜欢 async/await,则可以使用fs/promisesAPI。

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

const removeDir = async (dirPath) => {
  await fs.rm(dirPath, {recursive: true});
}
Run Code Online (Sandbox Code Playgroud)

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

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

const removeDir = async (dirPath) => {
  await fs.rm(dirPath, {recursive: true});
}
Run Code Online (Sandbox Code Playgroud)