Удалить каталог, который не пуст

В моем приложении Node мне нужно удалить каталог с несколькими файлами, но fs.rmdir работает только в пустых каталогах. Как я могу это сделать?

Ответ 1

Для этого существует модуль под названием rimraf (https://npmjs.org/package/rimraf). Он обеспечивает ту же функциональность, что и rm -Rf

Асинхронное использование:

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

Синхронизация использования:

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

Ответ 2

Удалить папку синхронно

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

Ответ 3

Большинство людей, использующих fs с Node.js, хотели бы, чтобы функции были близки к "Unix-способу" работы с файлами. Я использую fs-extra, чтобы принести все классные вещи:

fs-extra содержит методы, которые не включены в пакет vanilla Node.js fs. Такие как mkdir -p, cp -r и rm -r f.

Более того, 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);

Ответ 4

Мой измененный ответ от @oconnecp (fooobar.com/questions/57364/...)

Использует 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);
    }
}

Ответ 5

По состоянию на 2019 год...

С Node.js 12.10 вы, наконец, можете сделать простое:

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

Опция recursive рекурсивно удаляет все это.

Ответ 6

Вот асинхронная версия ответа @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);
            });
        });
    });
};

Ответ 7

Я написал эту функцию под названием 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)
            })
        })
    })
}

Ответ 8

Я обычно не воскрешаю старые темы, но здесь есть много чего, и без ответа, все это кажется мне слишком сложным.

Во-первых, в современном 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() функцией. Удалите актуальный каталог в конце. Исправить пропущенную рекурсию.

Ответ 9

Если вы используете узел 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);
    }
}

Ответ 10

Асинхронная версия ответа @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);
    }
};

Ответ 11

Я пришел сюда, пытаясь перебраться с gulp, и я пишу для дальнейших достижений.

Если вы хотите удалить файлы и папки с помощью del, вы должны добавить /** для рекурсивного удаления.

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

Ответ 12

В последней версии Node.js(12.10.0 или более поздней) функции стиля rmdir fs.rmdir(), fs.rmdirSync() и fs.promises.rmdir() есть новая экспериментальная опция recursive, которая позволяет удалять непустые каталоги, например,

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

Связанный PR на GitHub: https://github.com/nodejs/node/pull/29168

Ответ 13

Хотелось бы, чтобы был способ сделать это без дополнительных модулей для чего-то крошечного и общего, но это лучшее, что я мог придумать.

Обновление: теперь должно работать в 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);
    });
}

Ответ 14

Пакет де-факто - 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))
}

Ответ 15

Синхронизировать папку удалить с файлами или только с файлом.

Я не большой даритель и не вкладчик, но я не смог найти хорошего решения этой проблемы, и мне пришлось найти свой путь... так что я надеюсь, вам понравится :)

Идеально подходит для меня с любым количеством вложенных каталогов и подкаталогов. Предостережение для области действия 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);
        }

Ответ 16

Просто используйте модуль rmdir! это легко и просто.

Ответ 17

Другой альтернативой является использование 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)

note: для async/await требуется последняя версия nodejs (7.6 +)

Ответ 18

Быстрый и грязный способ (возможно, для тестирования) может состоять в том, чтобы напрямую использовать метод exec или spawn для вызова вызова ОС для удаления каталога. Читайте больше на NodeJs child_process.

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

Недостатками являются:

  1. Вы зависите от базовой ОС, то есть тот же метод будет работать в Unix/Linux, но, вероятно, не в Windows.
  2. Вы не можете угнать процесс на условиях или ошибках. Вы просто передаете задачу базовой ОС и ждете возврата кода завершения.

Выгоды:

  1. Эти процессы могут выполняться асинхронно.
  2. Вы можете прослушивать вывод/ошибку команды, следовательно, вывод команды не теряется. Если операция не завершена, вы можете проверить код ошибки и повторить попытку.

Ответ 19

Это один из подходов, использующих 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)
    }
}; 

Ответ 20

Сверхскоростной и надежный

Вы можете использовать пакет lignator (https://www.npmjs.com/package/lignator), он быстрее любого асинхронного кода (например, rimraf) и более надежен (особенно в Windows, где удаление файлов происходит не мгновенно и файлы могут быть заблокированы другими процессами).

4,36 GB of data, 28 042 files, 4 217 folders on Windows removed in 15 seconds vs rimraf 60 seconds on old HDD.

const lignator = require('lignator');

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

Ответ 21

//без использования каких-либо сторонних библиотек

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

Ответ 22

В то время как recursive является экспериментальной опцией fs.rmdir

function rm (path, cb) {
    fs.stat(path, function (err, stats) {
        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);
            });
        });
    });
}

Ответ 23

С использованием Promise, удалите каталог асинхронно..

MyObject.prototype.removeDir = function (path) {
    var executor = (resolve, reject)=> {
        var relativePath = "";
        //lets read the directory first
        fs.readdir(path, (err, files)=> {
            let fLen = files.length;
            //if this directory doesn't contain any file lets just delete it and then resolve or reject..
            //depending on whether there is an error or not
            if (fLen === 0) {
                fs.rmdir(path, (err)=> {
                    if (err) return reject(err); else return resolve(true)
                });
            }
            //remember its asynchronous... so the loop runs without a block
            for (let i = 0; i < fLen; i++) {
                //append each fileName to the directory path to get is relativePath
                relativePath = path + "/" + files[i];
                //we also need to be sure the file is indeed a file or a directory
                fs.stat(relativePath, (err, stats)=> {
                    if (err) {
                        console.log(err);
                        return err
                    }
                    if (stats.isFile()) {
                        fs.unlink(relativePath, (err)=> {
                            if (err) return reject(err);
                            console.log("fs.unlink");
                            //if we have gotten to the last file within the directory we can now delete it
                            if (i === fLen - 1) {
                                fs.rmdir(path, (err)=> {
                                    if (err) return reject(err); else return resolve(true)
                                });
                            }
                        });
                    }
                    //so if there is a directory within this directory we should delete it as well
                    else if (stats.isDirectory()) {
                        //recursively deletes directory within a directory
                        this.removeDir(relativePath);
                    }
                    console.log("fs.Stat");
                });
            }
        });
    };
    return new Promise(executor);
};

Ответ 24

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