Документация для fs.rmdir очень короткая и не объясняет поведение rmdir, когда каталог не пуст.
Q: Что произойдет, если я попытаюсь использовать этот API для удаления непустого каталога?
Документация для fs.rmdir очень короткая и не объясняет поведение rmdir, когда каталог не пуст.
Q: Что произойдет, если я попытаюсь использовать этот API для удаления непустого каталога?
Краткий ответ: 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 страниц Руководства, содержащего интерфейсы ядра.
Хотя использование сторонней библиотеки для такой вещи я не мог придумать более элегантное решение. Поэтому я закончил использование 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)->
Я писал об этой проблеме ровно.
Мое предыдущее решение ниже, хотя и простое, не является предпочтительным. Следующая функция - это синхронное решение; в то время как асинхронный режим может быть предпочтительным.
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/.
Вы хотите обработать все ответы; но идея достаточно проста.
Просто небольшая точка среди этой группы ответов, но я думаю, что это хорошо, чтобы указать на это.
Лично (и вообще) я бы предпочел использовать уже существующую библиотеку, если она доступна, для выполнения задачи. Использование уже существующей вещи означает для меня, и особенно в мире с открытым исходным кодом, использование и совершенствование уже существующей вещи, которая может оказаться в лучшем результате, чем сама по себе (я улучшаю то, что есть у кого-то другого сделано).
В этом случае при небольшом поиске я обнаружил модуль fs-extra, который также должен быть заменен на rimraf
и отвечает на необходимость для удаления рекурсивно каталогов (по-видимому, с асинхронными и синхронными версиями). Кроме того, у него есть большое количество звезд на github и, кажется, в настоящее время поддерживается: эти два условия, в дополнение к тому, что ответы на необходимость, делают это способ пойти (почти немного для меня) для меня.
fs.rmdir
не рекурсивный.
Вместо этого вы можете использовать рекурсивный модуль fs.readdir, например readdirp, чтобы найти все файлы и каталоги. Затем удалите все файлы, а затем все каталоги.
Для еще более простого решения посмотрите rimraf.
В 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
Использовать child_process.execFile быстрее.
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
Вот асинхронная рекурсивная версия, которая работает с 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;
}
Полагали, что это было хорошим предлогом для погружения в источник;)
Из того, что я могу сказать, fs.rmdir
привязан к функции rmdir от unistd.h. На странице POSIX для rmdir:
Функция rmdir() должна удалить каталог, имя которого задается формулой дорожка. Каталог должен быть удален только в том случае, если он является пустым каталогом.
Если каталог не является пустым каталогом, rmdir() завершится с ошибкой и установите errno на [EEXIST] или [ENOTEMPTY].
В дополнение к правильным "нет" ответам пакет rimraf обеспечивает рекурсивную функцию удаления. Он имитирует rm -rf
. Он также официально упакован от Ubuntu.
Я понимаю, что это не совсем отвечает на вопрос, но я думаю, что это может быть полезно для кого-то, кто ищет здесь в будущем (это было бы для меня!): я сделал небольшой фрагмент, который позволяет рекурсивно удалите только пустые каталоги. Если каталог (или любой из его потоковых каталогов) содержит контент внутри него, он остается один:
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;
};
В большинстве примеров, которые я вижу, существуют синхронные реализации рекурсивного удаления структуры папок в node.
Я также видел несколько асинхронных, которые действительно не работают хорошо.
Я написал и использовал один полностью асинхронный: https://gist.github.com/yoavniran/adbbe12ddf7978e070c0
Эта функция будет рекурсивно удалять каталог или файл, который вы укажете, синхронно:
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);
}
}
Я не тестировал это поведение функции, если:
Рекурсивный каталог удаления для Node.js
Оказалось, что модуль Node.js fs не имеет метода для рекурсивного удаления каталога и его содержимого. Вместо этого вы должны пройти через структуру каталогов и удалить атомарные элементы, то есть отдельные файлы и пустые каталоги. Таким образом, я нашел красивый Takuo Kihira в https://gist.github.com/2367067, сделанный в JavaScript, и решил сделать его версию CoffeeScript:
попытался сделать его безопасным, поскольку удаление синхронизации приведет к ошибке, если в это время используется файл или каталог.
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();
}
});
Вот прототип кофе 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
Чистая синхронная версия 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 и должны быть переносимыми.
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);
}
Это сообщение получало главный ответ от 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);
});
}); } }
Следуя ответу @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
});
Удивительно многословные и плохие ответы здесь...
Чтобы удалить непустой каталог в большинстве систем:
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.