Является ли node.js rmdir рекурсивным? Будет ли он работать с непустыми каталогами?
Документация для fs.rmdir очень короткая и не объясняет поведение rmdir, когда каталог не пуст.
Q: Что произойдет, если я попытаюсь использовать этот API для удаления непустого каталога?
Ответы
Ответ 1
Краткий ответ: node.js fs.rmdir()
вызывает POSIX rmdir()
; это удалит пустой каталог или вернет ошибку. В данном случае вызов вызовет функцию обратного вызова и передаст ошибку как исключение.
Проблема здесь в том, что документация node.js ссылается на POSIX:
Вступление Node.js API Документы Файловаясистема гласит:
Файловый ввод-вывод обеспечивается простыми обертками вокруг стандартных функций POSIX.
Это почти превращает вопрос в дубликат:
Есть ли список API/функций POSIX?
Описание для fs.rmdir
является кратким, но достаточным.
Asynchronous rmdir(2).
rmdir(2)
здесь является неявной ссылкой на документацию для rmdir() system call
. Число (2) здесь - это старое соглашение о man-страницах Unix для обозначения Раздела 2 страниц Руководства, содержащего интерфейсы ядра.
Ответ 2
Хотя использование сторонней библиотеки для такой вещи я не мог придумать более элегантное решение. Поэтому я закончил использование npm-модуля rimraf.
Установите его
npm install rimraf
Или установите его и сохраните в 'package.json'
(другие параметры сохранения можно найти в npm-install docs)
npm install --save rimraf
Затем вы можете сделать следующее:
rmdir = require('rimraf');
rmdir('some/directory/with/files', function(error){});
Или в Coffeescript:
rmdir = require 'rimraf'
rmdir 'some/directory/with/files', (error)->
Ответ 3
Я писал об этой проблеме ровно.
Мое предыдущее решение ниже, хотя и простое, не является предпочтительным. Следующая функция - это синхронное решение; в то время как асинхронный режим может быть предпочтительным.
deleteFolderRecursive = function(path) {
var files = [];
if( fs.existsSync(path) ) {
files = fs.readdirSync(path);
files.forEach(function(file,index){
var curPath = path + "/" + file;
if(fs.lstatSync(curPath).isDirectory()) { // recurse
deleteFolderRecursive(curPath);
} else { // delete file
fs.unlinkSync(curPath);
}
});
fs.rmdirSync(path);
}
};
[Изменить] Добавлен lstat вместо stat для предотвращения ошибок в символических ссылках
[Предыдущее решение]
Мое решение для этого довольно просто реализовать.
var exec = require('child_process').exec,child;
child = exec('rm -rf test',function(err,out) {
console.log(out); err && console.log(err);
});
Это уменьшено для этой страницы, но основная идея проста; выполните 'rm -r' в командной строке. Если ваше приложение должно работать с различными типами ОС, поместите его в функцию и используйте для этого команду if/else/.
Вы хотите обработать все ответы; но идея достаточно проста.
Ответ 4
Просто небольшая точка среди этой группы ответов, но я думаю, что это хорошо, чтобы указать на это.
Лично (и вообще) я бы предпочел использовать уже существующую библиотеку, если она доступна, для выполнения задачи. Использование уже существующей вещи означает для меня, и особенно в мире с открытым исходным кодом, использование и совершенствование уже существующей вещи, которая может оказаться в лучшем результате, чем сама по себе (я улучшаю то, что есть у кого-то другого сделано).
В этом случае при небольшом поиске я обнаружил модуль fs-extra, который также должен быть заменен на rimraf
и отвечает на необходимость для удаления рекурсивно каталогов (по-видимому, с асинхронными и синхронными версиями). Кроме того, у него есть большое количество звезд на github и, кажется, в настоящее время поддерживается: эти два условия, в дополнение к тому, что ответы на необходимость, делают это способ пойти (почти немного для меня) для меня.
Ответ 5
fs.rmdir
не рекурсивный.
Вместо этого вы можете использовать рекурсивный модуль fs.readdir, например readdirp, чтобы найти все файлы и каталоги.
Затем удалите все файлы, а затем все каталоги.
Для еще более простого решения посмотрите rimraf.
Ответ 6
В Node.js v12.10.0 добавлена опция recursive
в fs.rmdir
.
Поскольку fs.mkdir
поддерживает ту же опцию, начиная с v10.12.0, создание и удаление каталога может выполняться рекурсивно.
$ node --experimental-repl-await
# without recursive option -> error
> await fs.promises.mkdir('foo/bar')
Thrown:
[Error: ENOENT: no such file or directory, mkdir 'foo/bar'] {
errno: -2,
code: 'ENOENT',
syscall: 'mkdir',
path: 'foo/bar'
}
# with recursive option -> success
> await fs.promises.mkdir('foo/bar', { recursive: true })
undefined
# without recursive option -> error
> await fs.promises.rmdir('foo')
Thrown:
[Error: ENOTEMPTY: directory not empty, rmdir 'foo'] {
errno: -66,
code: 'ENOTEMPTY',
syscall: 'rmdir',
path: 'foo'
}
# with recursive option -> success
> await fs.promises.rmdir('foo', { recursive: true })
undefined
Ответ 7
Использовать child_process.execFile быстрее.
Документы NodeJS:
child_process.execFile похож на child_process.exec(), за исключением его * не выполняет подоболочку, а скорее указанный файл напрямую.
Это работает. Мимика rm -rf DIR...
var child = require('child_process');
var rmdir = function(directories, callback) {
if(typeof directories === 'string') {
directories = [directories];
}
var args = directories;
args.unshift('-rf');
child.execFile('rm', args, {env:process.env}, function(err, stdout, stderr) {
callback.apply(this, arguments);
});
};
// USAGE
rmdir('dir');
rmdir('./dir');
rmdir('dir/*');
rmdir(['dir1', 'dir2']);
Изменить. Я должен признать, что это не кросс-платформенный, не работает в Windows
Ответ 8
Вот асинхронная рекурсивная версия, которая работает с promises. Я использую библиотеку "Q", но каждый будет делать с несколькими изменениями (например, функция "fail" ).
Чтобы использовать его, мы должны сделать несколько простых оберток вокруг некоторых основных функций Node, а именно fs.stat, fs.readdir, fs.unlink и fs.rmdir, чтобы сделать их перспективными.
Вот они:
function getStat(fpath) {
var def = Q.defer();
fs.stat(fpath, function(e, stat) {
if (e) { def.reject(); } else { def.resolve(stat); }
});
return def.promise;
}
function readdir(dirpath) {
var def = Q.defer();
fs.readdir(dirpath, function(e, files) {
if (e) { def.reject(e); } else { def.resolve(files); }
});
return def.promise;
}
function rmFile(fpath) {
var def = Q.defer();
fs.unlink(fpath, function(e) { if(e) { def.reject(e); } else { def.resolve(fpath); }});
return def.promise;
}
function rmDir(fpath) {
var def = Q.defer();
fs.rmdir(fpath, function(e) { if(e) { def.reject(e); } else { def.resolve(fpath); }});
return def.promise;
}
Итак, вот рекурсивная функция rm:
var path = require('path');
function recursiveDelete(fpath) {
var def = Q.defer();
getStat(fpath)
.then(function(stat) {
if (stat.isDirectory()) {
return readdir(fpath)
.then(function(files) {
if (!files.length) {
return rmDir(fpath);
} else {
return Q.all(files.map(function(f) { return recursiveDelete(path.join(fpath, f)); }))
.then(function() { return rmDir(fpath); });
}
});
} else {
return rmFile(fpath);
}
})
.then(function(res) { def.resolve(res); })
.fail(function(e) { def.reject(e); })
.done();
return def.promise;
}
Ответ 9
Полагали, что это было хорошим предлогом для погружения в источник;)
Из того, что я могу сказать, fs.rmdir
привязан к функции rmdir от unistd.h. На странице POSIX для rmdir:
Функция rmdir() должна удалить каталог, имя которого задается формулой дорожка. Каталог должен быть удален только в том случае, если он является пустым каталогом.
Если каталог не является пустым каталогом, rmdir() завершится с ошибкой и установите errno на [EEXIST] или [ENOTEMPTY].
Ответ 10
В дополнение к правильным "нет" ответам пакет rimraf обеспечивает рекурсивную функцию удаления. Он имитирует rm -rf
. Он также официально упакован от Ubuntu.
Ответ 11
Я понимаю, что это не совсем отвечает на вопрос, но я думаю, что это может быть полезно для кого-то, кто ищет здесь в будущем (это было бы для меня!): я сделал небольшой фрагмент, который позволяет рекурсивно удалите только пустые каталоги. Если каталог (или любой из его потоковых каталогов) содержит контент внутри него, он остается один:
var fs = require("fs");
var path = require("path");
var rmdir = function(dir) {
var empty = true, list = fs.readdirSync(dir);
for(var i = list.length - 1; i >= 0; i--) {
var filename = path.join(dir, list[i]);
var stat = fs.statSync(filename);
if(filename.indexOf('.') > -1) {
//There are files in the directory - we can't empty it!
empty = false;
list.splice(i, 1);
}
}
//Cycle through the list of sub-directories, cleaning each as we go
for(var i = list.length - 1; i >= 0; i--) {
filename = path.join(dir, list[i]);
if (rmdir(filename)) {
list.splice(i, 1);
}
}
//Check if the directory was truly empty
if (!list.length && empty) {
console.log('delete!');
fs.rmdirSync(dir);
return true;
}
return false;
};
https://gist.github.com/azaslavsky/661020d437fa199e95ab
Ответ 12
В большинстве примеров, которые я вижу, существуют синхронные реализации рекурсивного удаления структуры папок в node.
Я также видел несколько асинхронных, которые действительно не работают хорошо.
Я написал и использовал один полностью асинхронный: https://gist.github.com/yoavniran/adbbe12ddf7978e070c0
Ответ 13
Эта функция будет рекурсивно удалять каталог или файл, который вы укажете, синхронно:
var path = require('path');
function deleteRecursiveSync(itemPath) {
if (fs.statSync(itemPath).isDirectory()) {
_.each(fs.readdirSync(itemPath), function(childItemName) {
deleteRecursiveSync(path.join(itemPath, childItemName));
});
fs.rmdirSync(itemPath);
} else {
fs.unlinkSync(itemPath);
}
}
Я не тестировал это поведение функции, если:
- элемент не существует, или
- элемент не может быть удален (например, из-за проблемы с разрешениями).
Ответ 14
Рекурсивный каталог удаления для Node.js
Оказалось, что модуль Node.js fs не имеет метода для рекурсивного удаления каталога и его содержимого. Вместо этого вы должны пройти через структуру каталогов и удалить атомарные элементы, то есть отдельные файлы и пустые каталоги. Таким образом, я нашел красивый Takuo Kihira в https://gist.github.com/2367067, сделанный в JavaScript, и решил сделать его версию CoffeeScript:
Ответ 15
попытался сделать его безопасным, поскольку удаление синхронизации приведет к ошибке, если в это время используется файл или каталог.
var path = require('path');
var fs = require('fs')
var dumpDirs = function (dir, name, cb) {
fs.readdir(dir, function (err, files) {
var dirs = [],
filePath, i = 0, l = files.length;
for (var i = 0; i < l; i++) {
filePath = path.join(dir, files[i]);
var stats = fs.lstatSync(filePath);
if (stats.isDirectory()) {
if (files[i].indexOf(name) != -1) {
dirs.push({
startOn: new Date(stats.ctime),
instance: files[i],
name: name
})
}
}
}
cb(dirs);
});
}
var removeDir = function (dir, callback) {
fs.readdir(dir, function (err, files) {
c = files.length;
(function remfile(i, cb) {
if (i >= c)
return cb();
var p = path.join(dir, files[i])
fs.unlink(p, function (err) {
if (err) console.log(err);
remfile(i + 1, cb)
});
})(0, function () {
fs.rmdir(dir, function (err) {
callback()
});
});
//for (var i = 0; i < c; i++) {
// fs.unlinkSync(path.join(dir, files[i]));
//};
});
}
dumpDirs(maindir, function (dirs) {
if (dirs && dirs.length > 0) {
(function rem(i, cb) {
if (i >= dirs.length) {
return cb();
}
var folder = path.join(dump, dirs[i].instance);
removeDir(folder, function () {
rem(i + 1, cb);
});
})(0, function () {
callback();
})
}
else {
callback();
}
});
Ответ 16
Вот прототип кофе script, который я создал для fluentnode, который рекурсивно удаляет папку
String::folder_Delete_Recursive = ->
path = @.toString()
if path.exists()
for file in path.files()
curPath = path.path_Combine(file)
if curPath.is_Folder()
curPath.folder_Delete_Recursive()
else
curPath.file_Delete()
fs.rmdirSync(path);
return path.not_Exists()
вот тест:
it 'folder_Create and folder_Delete' , ->
tmpDir = "./".temp_Name_In_Folder()
expect(tmpDir.folder_Exists()).to.be.false
expect(tmpDir.folder_Create()).to.equal(tmpDir.realPath())
expect(tmpDir.folder_Exists()).to.be.true
expect(tmpDir.folder_Delete()).to.be.true
expect(tmpDir.folder_Exists()).to.be.false
it 'folder_Delete_Recursive' , ->
tmpDir = "./" .temp_Name_In_Folder().folder_Create()
tmpFile = tmpDir.temp_Name_In_Folder().file_Create()
expect(tmpDir.folder_Delete_Recursive()).to.be.true
Ответ 17
Чистая синхронная версия rmdirSync.
/**
* use with try ... catch ...
*
* If you have permission to remove all file/dir
* and no race condition and no IO exception...
* then this should work
*
* uncomment the line
* if(!fs.exists(p)) return
* if you care the inital value of dir,
*
*/
var fs = require('fs')
var path = require('path')
function rmdirSync(dir,file){
var p = file? path.join(dir,file):dir;
// if(!fs.exists(p)) return
if(fs.lstatSync(p).isDirectory()){
fs.readdirSync(p).forEach(rmdirSync.bind(null,p))
fs.rmdirSync(p)
}
else fs.unlinkSync(p)
}
И параллельная IO, асинхронная версия rmdir. (Быстрее)
/**
* NOTE:
*
* If there are no error, callback will only be called once.
*
* If there are multiple errors, callback will be called
* exactly as many time as errors occur.
*
* Sometimes, this behavior maybe useful, but users
* should be aware of this and handle errors in callback.
*
*/
var fs = require('fs')
var path = require('path')
function rmfile(dir, file, callback){
var p = path.join(dir, file)
fs.lstat(p, function(err, stat){
if(err) callback.call(null,err)
else if(stat.isDirectory()) rmdir(p, callback)
else fs.unlink(p, callback)
})
}
function rmdir(dir, callback){
fs.readdir(dir, function(err,files){
if(err) callback.call(null,err)
else if( files.length ){
var i,j
for(i=j=files.length; i--; ){
rmfile(dir,files[i], function(err){
if(err) callback.call(null, err)
else if(--j === 0 ) fs.rmdir(dir,callback)
})
}
}
else fs.rmdir(dir, callback)
})
}
В любом случае, если вы хотите, чтобы последовательный IO и обратный вызов вызывались ровно один раз (либо успех, либо с первой встречной ошибкой). Замените этот rmdir выше. (Медленнее)
function rmdir(dir, callback){
fs.readdir(dir, function(err,files){
if(err) callback.call(null,err)
else if( files.length ) rmfile(dir, files[0], function(err){
if(err) callback.call(null,err)
else rmdir(dir, callback)
})
else fs.rmdir(dir, callback)
})
}
Все они зависят ТОЛЬКО от node.js и должны быть переносимыми.
Ответ 18
var fs = require('fs');
fs.delR = function(dir){
var s = fs.lstatSync(dir);
if(s.isFile())
fs.unlinkSync(dir);
if(!s.isDirectory())
return;
var fileArr = fs.readdirSync(dir);
for(f in fileArr)
fs.delR(dir+'/'+fileArr[f]);
fs.rmdirSync(dir);
}
Ответ 19
Это сообщение получало главный ответ от google, но ни один из ответов не дал решение, которое:
-
не использует функции синхронизации
-
не требует внешних библиотек
-
не использует bash напрямую
Вот мое решение async
, которое не предполагает ничего, кроме node:
const fs = require('fs'); const path = require('path');
function rm(path){
return stat(path).then((_stat) => {
if(_stat.isDirectory()){
return ls(path)
.then((files) => Promise.all(files.map(file => rm(Path.join(path, file)))))
.then(() => removeEmptyFolder(path));
}else{
return removeFileOrLink(path);
} });
function removeEmptyFolder(path){
return new Promise((done, err) => {
fs.rmdir(path, function(error){
if(error){ return err(error); }
return done("ok");
});
}); }
function removeFileOrLink(path){
return new Promise((done, err) => {
fs.unlink(path, function(error){
if(error){ return err(error); }
return done("ok");
});
}); }
function ls(path){
return new Promise((done, err) => {
fs.readdir(path, function (error, files) {
if(error) return err(error)
return done(files)
});
}); }
function stat(path){
return new Promise((done, err) => {
fs.stat(path, function (error, _stat) {
if(error){ return err(error); }
return done(_stat);
});
}); } }
Ответ 20
Следуя ответу @geedew.
Вот асинхронная реализация rm -r
(т.е. вы можете передать путь к файлу или каталогу). Я не опытный разработчик nodejs и ценю любые предложения или конструктивную критику.
var fs = require('fs');
function ResultsCollector (numResultsExpected, runWhenDone) {
this.numResultsExpected = numResultsExpected,
this.runWhenDone = runWhenDone;
this.numResults = 0;
this.errors = [];
this.report = function (err) {
if (err) this.errors.push(err);
this.numResults++;
if (this.numResults == this.numResultsExpected) {
if (this.errors.length > 0) return runWhenDone(this.errors);
else return runWhenDone();
}
};
}
function rmRasync(path, cb) {
fs.lstat(path, function(err, stats) {
if (err && err.code == 'ENOENT') return cb(); // does not exist, nothing to do
else if (err) {
return cb(err);
}
if (stats.isDirectory()) {
fs.readdir(path, function (err, files) {
if (err) return cb(err);
var resultsCollector = new ResultsCollector(files.length, function (err) {
if (err) return cb(err);
fs.rmdir(path, function (err) {
if (err) return cb(err);
return cb();
});
});
files.forEach(function (file) {
var filePath = path + '/' + file;
return rmRasync(filePath, function (err) {
return resultsCollector.report(err);
});
});
});
}
else { // file.
// delete file or link
fs.unlink(path, function (err) {
if (err) return cb(err);
return cb();
});
}
});
};
вызывать так:
rmRasync('/path/to/some/file/or/dir', function (err) {
if (err) return console.error('Could not rm', err);
// else success
});
Ответ 21
Удивительно многословные и плохие ответы здесь...
Чтобы удалить непустой каталог в большинстве систем:
import * as cp from 'child_process';
const dir = '/the/dir/to/remove';
const k = cp.spawn('bash');
k.stdin.end('rm -rf "${dir}"');
k.once('exit', code => {
// check the exit code
// now you are done
});
это будет работать на MacOS и Linux, но может не работать на некоторых ОС Windows.