删除不为空的目录


298

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


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

Answers:


319

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

异步用法:

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

同步使用情况:

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

1
奇怪,我从未见过这样的行为。我建议搜索和/或提交错误。github.com/isaacs/rimraf/issues
Morgan ARR艾伦(Allen Allen),

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

4
@EmettSpeer什么时候表示“轻松完成”?自写deleteFolderRecursive下面的答案中的功能?
Freewind

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

3
您现在可以使用一个recursive选项:stackoverflow.com/a/57866165/6269864

243

同步删除文件夹

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

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

9
更可靠地实现:更换var curPath = path + "/" + file;var curPath = p.join(path, file);提供包括你的路径模块:var p = require("path")
安德里·

9
Windows有\斜线,所以path.join(dirpath, file)应该比path + "/" + file
-thybzi

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

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

167

fs与Node.js一起使用的大多数人都希望函数类似于处理文件的“ Unix方式”。我正在使用fs-extra带来所有很酷的东西:

fs-extra包含香草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);

所需的同步版本removeSync('/tmp/myFolder')
olidem

148

截至2019年...

Node.js 12.10.0版本开始,它fs.rmdirSync支持recursive选项,因此您最终可以执行以下操作:

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

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


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

9
从节点v13.0.1起,递归删除仍处于实验阶段
蒂姆(Tim)

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

@Tim实验是什么意思?
美洲

2
@Emerica在官方的node.js文档中,有一个大大的橙色通知,说它fs.rmdir具有稳定性1。未来版本。不建议在生产环境中使用此功能。”
蒂姆

24

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

17

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

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

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

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


1
这是一个非常整洁的解决方案。关于第二个代码示例问:请你不要叫await上你的Promise.all(…); 这是故意的吗?在当前状态下results.forEach,代码似乎希望对结果进行迭代。我想念什么吗?
安东·斯特罗诺诺夫

@Tony,您是正确的,它是拼写错误/错误。接得好!
Sukima

也许先检查一下以确保目录存在?就像if (!fs.existsSync(dir)) return
GTPV '19

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

我绝对明白你的意思。我直觉地希望尝试删除不存在的文件夹会成功,因为它只会无所事事。如果使用的同步版本,fs.exists则没有竞争条件。PS这是一个很好的解决方案。
GTPV

12

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

10

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

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

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

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

8

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

4

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

3

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

当您想使用删除文件和文件夹时del,应追加/**以进行递归删除。

gulp.task('clean', function () {
    return del(['some/path/to/delete/**']);
});

2

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


2

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

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

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


1

超高速和防故障

您可以使用该lignator软件包(https://www.npmjs.com/package/lignator),它比任何异步代码(例如rimraf)都要快,并且具有更高的防故障能力(尤其是在Windows中,文件删除不是即时的,并且文件可能被其他进程锁定)。

Windows上在15秒内删除了4,36 GB的数据,28042个文件,4 217个文件夹,而rimraf在旧硬盘上则是60秒

const lignator = require('lignator');

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

1

同步文件夹随文件或仅文件一起删除。

我既不是献礼者,也不是贡献者,但是我找不到解决这个问题的好方法,因此我不得不找到办法...所以我希望您会喜欢:)

在许多情况下对我来说都很完美嵌套目录和子目录。递归函数时,请注意“ 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);
        }

1

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

1

2020更新

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

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

因此,您可以进行同步:

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

或异步:

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


0

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


0

一种快速而肮脏的方法(可能是用于测试)可能是直接使用execor spawn方法来调用OS调用以删除目录。阅读更多关于NodeJs child_process的信息

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

缺点是:

  1. 您取决于基础操作系统,即,相同的方法将在UNIX / Linux中运行,但可能不在Windows中运行。
  2. 您无法根据条件或错误劫持该过程。您只需将任务交给底层操作系统,然后等待返回退出代码即可。

好处:

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

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

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

0

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

更新:现在应该可以在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是可行的方法。
2016年

3
这种方法非常危险。shell命令的字符串连接,尤其是没有转义的字符串连接,会导致代码执行漏洞等。如果要使用rmdir,请使用child_process.execFilewhich不会调用外壳程序,而是显式传递参数。
nevyn

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

始终不愿使用第三方!谢谢!
安东·米采夫

除此之外,此方法相当慢。Nodejs本机api更快。
默西

0

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

0

//不使用任何第三方库

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

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

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

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

是的,既适用于嵌套目录,也适用于非嵌套目录
Erisan Olasheni,
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.