Видаліть каталог, який не порожній


299

У моєму додатку Node мені потрібно видалити каталог, який містить деякі файли, але fs.rmdirпрацює лише в порожніх каталогах. Як я можу це зробити?


1
Коротше кажучи: fs.readdir(dirPath)для масиву шляхів у папці перейдіть, fs.unlink(filename)щоб видалити кожен файл, а потім, нарешті, fs.rmdir(dirPath)для видалення порожньої папки. Якщо вам потрібно повторити, перевірте fs.lstat(filename).isDirectory().
іоносферні

Відповіді:


319

Існує модуль для цього під назвою rimraf( https://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/isissue
Morgan ARR Allen

35
Це те, що можна легко зробити за допомогою NodeJS Core libs. Навіщо встановлювати непідтримуваний сторонній пакет?
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 є кореневим dir.
Jake_Howard

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 клієнт, а не більше. Отже, не потрібно використовувати асинхронізацію для консольного додатка в цьому випадку
Леонід Дашко,

4
Я отримую "Помилка: ENOTEMPTY: каталог не порожній"
Чайка

168

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

fs-extra містить методи, які не входять у ванільний пакет Node.js fs. Такі як mkdir -p, cp -r і rm -rf.

Ще краще, що fs-extra - це падіння заміни на рідні. Усі методи у 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 0

9
Рекурсивне видалення досі експериментально станом на вузол v13.0.1
Тім

5
Функція підпису на самому справі fs.rmdir(path[, options], callback)абоfs.rmdirSync(path[, options])
conceptdeluxe

@Тим, що ти маєш на увазі під експериментальним?
Emerica

2
@Emerica В офіційних документах node.js є велике помаранчеве повідомлення, яке fs.rmdirє експериментальним щодо стабільності 1. "Стабільність: 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

Я, як правило, не відроджую старі теми, але тут дуже багато, і я не можу відповісти на це, мені все це надто складно.

По-перше, у сучасному 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);
};

EDIT: Зробіть isDirectory()функцію. Видаліть фактичний каталог наприкінці. Виправити відсутні рекурсії.


1
Це дійсно акуратне рішення. Питання про другий прикладі коду: ви не викликаєте awaitна вашому Promise.all(…); це навмисно? Схоже, що в його нинішньому стані він results.forEachби повторював обіцянки, тоді як код очікує ітерації над результатами. Я щось пропускаю?
Антон Строгонов

@Тоні ви праві, це помилка помилки. Гарний улов!
Сукіма

Може спочатку перевірити, щоб переконатися, що каталог існує? щось на кшталтif (!fs.existsSync(dir)) return
GTPV

@GTPV Чому? Це збільшує відповідальність цієї функції. readdirвидасть помилку як слід. Якщо у вас rmdir non-existing-dirвихідний код - це помилка. Спроба / виловлення повинна бути відповідальною за споживача. Це той самий метод, описаний у документах Node, коли мова йде про використання функцій fs. Вони очікують, що ви спробуєте / наздоганите і подивіться на помилки, codeщоб визначити, що робити. Додаткова перевірка вводить умову гонки.
Сукіма

Я точно бачу вашу думку. Я хоч би інтуїтивно очікував, що спроба видалити папку, яка не існує, була б успішною, оскільки вона просто нічого не зробить. Немає умови перегонів, якщо використовується синхронна версія 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+, який хоче асинхронність і не хочете зовнішніх залежностей, ось версія 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);
    }
}

4

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

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, де видалення файлів не миттєве і файли можуть бути заблокованими іншими процесами).

4,36 ГБ даних, 28 042 файли, 4 217 папок у Windows видалено за 15 секунд проти 60 секунд на старий жорсткий диск rimraf .

const lignator = require('lignator');

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

1

Синхронізуйте видалення папки з файлами або лише файлом.

Я не є великим дачником і не працюючим, але я не зміг знайти гарне рішення цієї проблеми, і я повинен був знайти свій шлях ... тож сподіваюся, вам це сподобається :)

Мені ідеально підходить для будь-якої кількості вкладені каталоги та підкаталоги. Застереження щодо обсягу "цього" під час повторної функції, ваша реалізація може бути різною. У моєму випадку ця функція перетворюється на повернення іншої функції, саме тому я називаю це саме цим.

    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

Поки recursiveє експериментальним варіантомfs.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 для опцій додано рекурсивну опцію.

Зауважте, що рекурсивне видалення експериментальне .

Отже, ви б зробили для синхронізації:

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

або для асинхронізації:

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

0

Просто використовуйте модуль rmdir ! це легко і просто.


6
Не завжди корисно використовувати модуль для кожного невеликого фрагмента коду. Якщо вам потрібно створити ліцензійну угоду, наприклад, це породжує справжній біль.
Міджаго

4
вам потрібно додати зразок коду, щоб відповідь була цікавішою
Xeltor

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)

Примітка: для асинхронізації / очікування потрібна остання версія nodejs (7.6+)


0

Швидкий і брудний спосіб (можливо, для тестування) може бути прямим використанням методу execабо spawnдля виклику виклику ОС для видалення каталогу. Детальніше про NodeJs child_process .

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

Нижніми сторонами є:

  1. Ви залежите від основної ОС, тобто той самий метод буде працювати в unix / linux, але, ймовірно, не в Windows.
  2. Ви не можете викрасти процес за умов чи помилок. Ви просто даєте завдання базовій ОС і чекаєте повернення коду виходу.

Переваги:

  1. Ці процеси можуть працювати асинхронно.
  2. Ви можете прослуховувати вихід / помилку команди, отже, вихід команди не втрачається. Якщо операція не завершена, ви можете перевірити код помилки та повторити спробу.

2
Ідеально підходить, коли ви пишете сценарій і не хочете встановлювати залежності, оскільки ви збираєтесь видалити цей скрипт через 30 секунд після того, як ви видалили всі свої файли !!
Матіас

Завжди є способи зіпсувати і видалити кореневу файлову систему. У цьому випадку ОП може зняти -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 - це шлях, якщо вам потрібен один модуль.
b01

3
Цей спосіб прямо небезпечний. Рядок об'єднання команди оболонки, особливо без уникнення, запрошує вразливості виконання коду та подібні. Якщо ви збираєтеся використовувати rmdir, використовуйте, child_process.execFileякий не викликає оболонку, і передайте аргументи явно замість цього.
невин

@nevyn Спробую оновити свою відповідь, якщо це працює.
b01

Завжди віддайте перевагу не використовувати сторонніх осіб! Дякую!
Антон Міцев

На додаток до цього, цей метод досить повільний. Nodejs рідні api набагато швидше.
Мерсі

0

Це один підхід, який використовує промотифікацію та дві функції допомоги (до і всім) для вирішення обіцянки.

Він робить усі дії асинхронними.

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
Це буде працювати для того, що мені потрібно, але ви, можливо, захочете використовувати шлях, а не fs.unllinkSync(path.join(FOLDER_PATH, element);
об'єднувати

-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
Я думаю, що щось подібне повинно працювати для вкладених каталогів.
crazy4jesus

Так, і для вкладеного каталогу, і для вкладеного
Ерісан Олашені
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.