在我的Node应用程序中,我需要删除包含一些文件的目录,但是fs.rmdir仅适用于空目录。我该怎么办?

评论

简而言之:fs.readdir(dirPath)表示文件夹中的路径数组,依次遍历fs.unlink(filename)删除每个文件,最后遍历fs.rmdir(dirPath)删除当前为空的文件夹。如果需要递归,请检查fs.lstat(filename).isDirectory()。

#1 楼

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

异步使用:

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


同步使用:

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


评论


使用NodeJS Core库可以轻松完成此操作,为什么要安装未维护的第三方软件包?

– SudoKid
18年6月5日在23:07

@EmettSpeer什么时候表示“轻松完成”?在以下答案中自我编写像deleteFolderRecursive之类的函数?

–自由风
18/09/10在8:59

“但是即使功能低于其最佳性能,然后也会向系统中添加不必要的程序包。”我非常不同意。您绝对没有理由彻底改造轮子,这是第19百万次,并且有可能在此过程中引入错误或安全漏洞。至少这是浪费时间。 Inb4“如果他们丢弃软件包,该怎么办”:万一从npm注册表中删除了软件包,您极有可能将其替换为自己的软件包。折断头之前没有必要包扎头。

– Demonblack
18-10-16在13:22



现在您可以使用del(npmjs.com/package/del)代替@rimraf

– Alessio Campanelli
18-10-26在22:59

您现在可以使用递归选项:stackoverflow.com/a/57866165/6269864

–user6269864
19年9月10日在12:30

#2 楼

从2019年开始...

从Node.js 12.10.0开始,fs.rmdirSync支持recursive选项,因此您最终可以做到:

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


其中recursive选项以递归方式删除整个目录。

评论


@anneb如果使用的是较旧版本的Node.js(<12.10),则会发生这种情况。最新版本可识别递归选项:true,并删除非空文件夹而不会引起投诉。

– GOTO 0
19-09-16在7:52

从节点v13.0.1开始,递归删除仍处于试验阶段

– Tim
19-10-24在16:51



函数签名实际上是fs.rmdir(path [,options],callback)或fs.rmdirSync(path [,options])

–conceptdeluxe
19年11月27日在22:51

@Emerica在官方的node.js文档中,有一个大大的橙色通知,指出fs.rmdir处于实验性且具有稳定性1。“稳定性:1-实验性。此功能不受语义版本控制规则的约束。向后兼容的更改或删除可能在将来的任何发行版中都会出现。不建议在生产环境中使用此功能。”

– Tim
20-4-20在18:01



递归rmdir在内部使用rimraf,如拉请求github.com/nodejs/node/pull/29168/files中所示

– kaznovac
20年4月21日在2:53

#3 楼

同步删除文件夹

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


评论


可能要添加一些检查,以确保您不会意外地在“ /”上运行此检查。例如,传递空路径和文件中的错字可能会导致curPath是根目录。

–杰克·霍华德
16 Mar 26 '16 at 14:48

更健壮的实现:替换var curPath = path +“ /” + file;与var curPath = p.join(path,file);前提是您包括路径模块:var p = require(“ path”)

–安德里
16年8月13日在6:47

Windows带有\斜杠,因此path.join(dirpath,file)应该比path +“ /” + file更好

–thybzi
17-2-28在10:03



由于在一个滴答时间内执行了太多操作,因此此代码可能会导致“超出最大调用堆栈大小”。 @Walf如果运行控制台应用程序,则只有1个客户端,而不是更多。因此,在这种情况下,无需为控制台应用程序使用异步

–莱昂尼德·达什科(Leonid Dashko)
18-2-28在22:16



我收到“错误:ENOTEMPTY:目录不为空”

–海鸥
18-09-20在6:34

#4 楼

使用带有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);


评论


对于同步版本,您需要removeSync('/ tmp / myFolder')

–olidem
20-05-22在16:18



#5 楼

我从@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);
    }
}


#6 楼

我通常不复活旧线程,但是这里有很多搅动,并且没有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()起作用。最后删除实际目录。修复缺少的递归。

评论


这是一个非常整洁的解决方案。有关第二个代码示例的问题:您不会在Promise.all(…)上调用await。这是故意的吗?似乎在当前状态下,结果forfor将遍历Promise,而代码则希望遍历结果。我想念什么吗?

– Anton Strogonoff
19年7月2日在23:09

@Tony您是正确的,它是一个错字/错误。接得好!

– Sukima
19年7月2日在23:31

也许先检查一下以确保目录存在?类似于if(!fs.existsSync(dir))返回

– GTPV
19年7月10日在14:12

@GTPV为什么?这增加了该功能的责任。 readdir会引发错误。如果rmdir non-existing-dir,则退出代码是错误。尝试/捕捉是消费者的责任。使用fs函数时,这与Node docs中描述的方法相同。他们希望您尝试/捕获并查看错误的代码,以确定该怎么做。额外的检查会引入比赛条件。

– Sukima
19年7月10日在14:15

我绝对明白你的意思。我直觉地希望尝试删除不存在的文件夹将是成功的,因为它只会执行任何操作。如果使用fs.exists的同步版本,则没有争用条件。附言这是一个很好的解决方案。

– GTPV
19年7月10日在14:51



#7 楼

这是@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);
            });
        });
    });
};


#8 楼

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

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


评论


这个想法实际上是如果已经由别人编写了自己的代码,则不要编写自己的代码。更好的方法是使用rimraf或fs-extra或任何其他节点模块为您完成工作。

– Victor Pudeyev
2014年10月2日22:53

是的,编写自己的代码很糟糕,因为在大型应用程序中使用数十个第三方模块进行相对琐碎的操作从未证明有任何缺点。

–埃里克
15年5月23日在5:03



#9 楼

如果您使用的节点8+需要异步性且不希望外部依赖关系,则为异步/等待版本:

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


#10 楼

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


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

#11 楼

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


#12 楼

我到达这里时正试图越过gulp,我正在写进一步的书信。



gulp-clean已弃用gulp-rimraf


不建议使用gulp-rimraf,而建议使用delete-files-folders


如果要使用del删除文件和文件夹,则应附加/**以进行递归删除。 />

#13 楼

根据fs文档,fsPromises当前提供了一个实验性的recursive选项,至少就我个人而言,在Windows上,它会删除该目录及其中的任何文件。

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


recursive: true可以删除Linux和MacOS上的文件吗?

评论


截至2020年,这是最好的答案。它正在MacOS上运行

– Bentael
20/12/3在15:54

#14 楼

一种快速而肮脏的方法(也许是用于测试)可能是直接使用execspawn方法来调用OS调用以删除目录。有关NodeJ child_process的更多信息。

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


缺点是:


您依赖于底层操作系统,即可以运行相同的方法在UNIX / Linux中,但可能不在Windows中。
您不能在条件或错误下劫持该进程。您只需将任务交给底层操作系统,然后等待退出代码返回即可。

好处:


这些进程可以异步运行。
您可以侦听命令的输出/错误,因此不会丢失命令输出。如果操作未完成,则可以检查错误代码并重试。


评论


非常适合当您编写脚本并且不想安装依赖项时使用,因为您将在删除所有文件后30秒内删除该脚本!

–数学家
18年1月1日于20:18

总有混乱和删除根文件系统的方法。在这种情况下,OP可以删除-f标志以确保安全,或者在键入时确保他/她将不删除所有内容。 exec + rm是我在测试期间经常使用的节点中有效且有用的命令。

–皮疹
19年5月4日在3:49

#15 楼

实际上的软件包是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))
}



#16 楼

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

< pre class =“ lang-js prettyprint-override”> fs.rmdir(path, { recursive: true });

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

#17 楼

2020更新

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

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

所以您可以这样做同步:

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


或异步:

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


#18 楼

超高速且具有故障预防功能

您可以使用lignator软件包(https://www.npmjs.com/package/lignator),它比任何异步代码(例如rimraf)都要快,而且失败更多证明(尤其是在Windows中,文件删除不是立即进行的,并且文件可能被其他进程锁定)。


4,36 GB的数据,28,042个文件,4,217个文件夹Windows在15秒内移走了Windows,而rimraf在旧HDD上花费了60秒。


const lignator = require('lignator');

lignator.remove('./build/');


#19 楼

Sync文件夹只删除文件或仅删除文件。
我既不是送礼者,也不是贡献者,但是我找不到解决此问题的好方法,所以我不得不找到办法...所以我希望您会喜欢:)
任意数量的嵌套目录和子目录对我来说都很完美。递归函数时,请注意“ 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);
        }


#20 楼

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


#21 楼

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

评论


在每个小的代码段中使用模块并不总是一个好主意。例如,如果您必须创建许可协议,则会产生很大的痛苦。

–米贾戈
17年2月8日在7:18

您需要添加代码示例以使答案更有趣

– Xeltor
17年8月28日在16:43

#22 楼

另一种选择是使用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+)

#23 楼

我希望有一种方法可以在没有任何附加模块的情况下实现如此微小和通用的功能,但这是我能想到的最好的方法。

更新:
现在应该可以在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-extra是您的最佳选择。

–b01
16年9月4日在12:39

这种方法非常危险。 Shell命令的字符串连接,尤其是没有转义的字符串连接,会引起代码执行漏洞和类似漏洞。如果要使用rmdir,请使用不调用Shell的child_process.execFile,而是显式传递参数。

– nevyn
18年3月14日在12:29

@nevyn如果可以,我将尝试尝试并更新我的答案。

–b01
18年3月16日在16:55

始终不愿使用第三方!谢谢!

–安东·米采夫(Anton Mitsev)
18-10-12在10:03

除此之外,这种方法非常慢。 Nodejs本机api更快。

–默西
19/12/12在12:27

#24 楼

这是一种使用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)
    }
}; 
 


#25 楼

//不使用任何第三方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);


评论


这将满足我的需要,但是您可能想使用路径而不是将斜杠串联起来:fs.unllinkSync(path.join(FOLDER_PATH,element);

– jackofallcode
19年8月21日在9:27



#26 楼

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


#27 楼

🚀graph-fs


安装
npm i graph-fs


使用
 const {Node} = require("graph-fs");
const directory = new Node("/path/to/directory");

directory.delete(); // <--
 


#28 楼

2020年答案
如果要在npm脚本中执行此操作,则使用命令npx无需安装任何第三方软件包。例如,如果要删除dist和.cache,当您运行npm run clean时,只需将此命令添加到package.json
{
  "scripts": {
    "clean": "npx rimraf dist .cache"
  }
}

它将在任何操作系统中运行

#29 楼

 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!')
  }
}
 


评论


我认为类似这样的东西应该适用于嵌套目录。

–fool4耶稣
18年11月6日在17:17

是的,既适用于嵌套目录,也适用于非嵌套目录

– Erisan Olasheni
18年11月7日在11:23