Die Dokumentation für fs.rmdir ist sehr kurz und erklärt nicht das Verhalten von rmdir, wenn das Verzeichnis nicht leer ist.
F : Was passiert, wenn ich versuche, mit dieser API ein nicht leeres Verzeichnis zu löschen?
Die Dokumentation für fs.rmdir ist sehr kurz und erklärt nicht das Verhalten von rmdir, wenn das Verzeichnis nicht leer ist.
F : Was passiert, wenn ich versuche, mit dieser API ein nicht leeres Verzeichnis zu löschen?
Antworten:
Kurze Antwort: node.js fs.rmdir()
ruft den POSIX auf rmdir()
; Dadurch wird ein leeres Verzeichnis entfernt oder ein Fehler zurückgegeben . Im gegebenen Fall ruft der Aufruf die Rückruffunktion auf und übergibt den Fehler als Ausnahme.
Das Problem hierbei ist, dass sich die Dokumentation zu node.js auf POSIX bezieht :
Die Node.js API Docs File System API wurde als gestartet
einfache Wrapper um Standard-POSIX-Funktionen.
Dies verwandelt die Frage fast in ein Duplikat von: Gibt es eine Auflistung der POSIX-API / -Funktionen?
Die Beschreibung für fs.rmdir
ist knapp, aber ausreichend.
Asynchrones rmdir (2).
Das rmdir(2)
hier ist ein impliziter Verweis auf die Dokumentation für das rmdir() system call
. Die Nummer (2) hier ist eine alte Unix-Manpage-Konvention, die Abschnitt 2 der Handbuchseiten angibt, der die Kernel-Schnittstellen enthält.
rmdir
rekursiv aufzurufen
Obwohl ich für so etwas eine Bibliothek eines Drittanbieters verwende, könnte ich keine elegantere Lösung finden. Also habe ich das npm-Modul rimraf benutzt .
Es installieren
npm install rimraf
Oder installieren Sie es und speichern Sie es in 'package.json' (weitere Speicheroptionen finden Sie in den npm-Installationsdokumenten ).
npm install --save rimraf
Dann können Sie Folgendes tun:
rmdir = require('rimraf');
rmdir('some/directory/with/files', function(error){});
Oder in Coffeescript:
rmdir = require 'rimraf'
rmdir 'some/directory/with/files', (error)->
require('./node_modules/rimraf');
- Sie könnten nurrequire('rimraf');
fs.rmdir
unterstützt die recursive
Option seit Node.js v12.10.0. Siehe meine Antwort .
Ich habe genau über dieses Problem geschrieben .
Meine vorherige Lösung unten ist zwar einfach, wird aber nicht bevorzugt. Die folgende Funktion ist eine synchrone Lösung. während Async bevorzugt werden könnte.
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);
}
};
[Bearbeiten] lstat anstelle von stat hinzugefügt, um Fehler bei Symlinks zu vermeiden
[Vorherige Lösung]
Meine Lösung hierfür ist recht einfach zu implementieren.
var exec = require('child_process').exec,child;
child = exec('rm -rf test',function(err,out) {
console.log(out); err && console.log(err);
});
Dies ist für diese Seite abgespeckt, aber die Grundidee ist einfach; Führen Sie 'rm -r' in der Befehlszeile aus. Wenn Ihre App auf verschiedenen Betriebssystemtypen ausgeführt werden muss, fügen Sie dies in eine Funktion ein und verfügen Sie über einen if / else / -Schalter, um damit umzugehen.
Sie möchten alle Antworten verarbeiten. aber die Idee ist einfach genug.
fs.rmdir
und es wird auch unter Windows nicht funktionieren.
stat()
als es hätte verwendet werden sollen lstat()
, was auch bedeutet, dass es dem Link folgte und den Inhalt des Zielverzeichnisses löschte, bevor es fehlerhaft wurde! Eek! Ich schlug eine Änderung vor, um sie zu ändern lstat
.
path.join(path, file)
anstelle vonpath + "/" + file
Node.js v12.10.0 hat die recursive
Option in eingeführt fs.rmdir
. Da fs.mkdir
dieselbe Option seit Version 10.12.0 unterstützt wird, können das Erstellen und Entfernen von Verzeichnissen rekursiv ausgeführt werden.
$ 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
Nur ein kleiner Punkt unter diesen Antworten, aber ich denke, es ist gut, darauf hinzuweisen.
Persönlich (und allgemein) würde ich es vorziehen, eine bereits vorhandene Bibliothek, falls verfügbar, für die Ausführung der Aufgabe zu verwenden. Eine bereits existierende Sache zu nehmen bedeutet für mich und insbesondere in der Open Source-Welt, eine bereits existierende Sache zu nutzen und zu verbessern, was zu einem besseren Ergebnis führen könnte, als es alleine zu tun (ich verbessere etwas, das jemand anderes hat erledigt).
In diesem Fall fand ich mit einer kleinen Suche das Modul fs-extra heraus , das auch als Ersatz für rimraf
die Notwendigkeit dienen soll, rekursiv Verzeichnisse zu entfernen (anscheinend mit asynchronen und synchronisierten Versionen). Darüber hinaus hat es eine gute Anzahl von Sternen auf Github und scheint derzeit erhalten zu sein: Diese beiden Bedingungen, zusätzlich zu der Tatsache, dass Antworten auf die Bedürfnisse, machen es für mich zum richtigen Weg (fast für ein bisschen).
Verwenden Sie child_process.execFile, es ist schneller .
child_process.execFile ähnelt child_process.exec (), außer dass * keine Subshell ausgeführt wird, sondern die angegebene Datei direkt.
Das funktioniert. Nachahmungrm -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']);
Bearbeiten : Ich muss zugeben, dass dies nicht plattformübergreifend ist, funktioniert nicht unter Windows
Hier ist eine asynchrone rekursive Version, die mit Versprechungen arbeitet. Ich benutze die 'Q'-Bibliothek, aber jeder wird ein paar Änderungen vornehmen (zB die' Fail'-Funktion).
Um davon Gebrauch zu machen, müssen wir einige einfache Wrapper um einige Kernknotenfunktionen erstellen, nämlich fs.stat, fs.readdir, fs.unlink und fs.rmdir, um sie vielversprechend zu machen.
Hier sind sie:
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;
}
Hier ist also die rekursive rm-Funktion:
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;
}
Q.ninvoke(fs, "rmdir"...)
?
Ich dachte, das wäre eine gute Ausrede, um in die Quelle einzutauchen;)
fs.rmdir
Soweit ich das beurteilen kann, ist die Funktion rmdir von unistd.h an gebunden. Aus der POSIX-Manpage für rmdir :
Die Funktion rmdir () entfernt ein Verzeichnis, dessen Name durch den Pfad angegeben wird. Das Verzeichnis darf nur entfernt werden, wenn es sich um ein leeres Verzeichnis handelt.
Wenn das Verzeichnis kein leeres Verzeichnis ist, schlägt rmdir () fehl und setzt errno auf [EEXIST] oder [ENOTEMPTY].
Zusätzlich zu den richtigen "Nein" -Antworten bietet das rimraf- Paket rekursive Löschfunktionen . Es ahmt nach rm -rf
. Es ist auch offiziell von Ubuntu verpackt .
Mir ist klar, dass dies die vorliegende Frage nicht genau beantwortet, aber ich denke, dass dies für jemanden nützlich sein könnte, der hier in Zukunft sucht (es wäre für mich gewesen!): Ich habe einen kleinen Ausschnitt erstellt, mit dem man rekursiv nur leere löschen kann Verzeichnisse . Wenn ein Verzeichnis (oder eines seiner untergeordneten Verzeichnisse) Inhalt enthält, wird es in Ruhe gelassen:
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;
};
Die meisten Beispiele, die ich dort sehe, sind synchrone Implementierungen des rekursiven Löschens einer Ordnerstruktur im Knoten.
Ich habe auch einige asynchrone gesehen, die nicht wirklich gut funktionieren.
Ich habe eine vollständig asynchrone geschrieben und verwendet: https://gist.github.com/yoavniran/adbbe12ddf7978e070c0
Diese Funktion löscht rekursiv ein Verzeichnis oder eine Datei, die Sie angeben, synchron:
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);
}
}
Ich habe das Verhalten dieser Funktion nicht getestet, wenn:
Rekursives Verzeichnis zum Entfernen für Node.js.
Es stellte sich heraus, dass das Node.js fs-Modul keine Methode zum rekursiven Entfernen des Verzeichnisses und seines Inhalts hat. Stattdessen sollten Sie die Verzeichnisstruktur durchgehen und atomare Elemente entfernen, dh einzelne Dateien und leere Verzeichnisse. Also fand ich eine nette Zusammenfassung von Takuo Kihira unter https://gist.github.com/2367067, die in JavaScript erstellt wurde, und entschied mich , eine CoffeeScript-Version davon zu erstellen :
Es wurde versucht, die Ausfallsicherheit zu gewährleisten, da das Entfernen der Synchronisierung zu Fehlern führt, wenn zu diesem Zeitpunkt Datei oder Verzeichnis verwendet werden.
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();
}
});
Hier ist die Prototypfunktion des Kaffeeskripts , die ich für fluentnode erstellt habe und die einen Ordner rekursiv löscht
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()
Hier ist der Test:
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
Eine ordentliche synchrone Version von 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)
}
Und eine parallele asynchrone E / A-Version von rmdir. (schneller)
/**
* 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)
})
}
Auf jeden Fall, wenn Sie eine sequentielle E / A wünschen und der Rückruf genau einmal aufgerufen werden soll (entweder erfolgreich oder mit dem ersten aufgetretenen Fehler). Ersetzen Sie dieses rmdir durch das oben genannte. (Langsamer)
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)
})
}
Alle von ihnen hängen NUR von node.js ab und sollten portabel sein.
Dieser Beitrag erhielt die beste Antwort von Google, aber keine der Antworten bietet eine Lösung, die:
nutzt keine Synchronisierungsfunktionen
benötigt keine externen Bibliotheken
verwendet bash nicht direkt
Hier ist meine async
Lösung, die nichts anderes als den installierten Knoten voraussetzt:
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);
});
}); } }
Folgen Sie der Antwort von @ geedew.
Hier ist eine asynchrone Implementierung von rm -r
(dh Sie können einen Pfad zu einer Datei oder einem Verzeichnis übergeben). Ich bin kein erfahrener NodeJS-Entwickler und freue mich über Vorschläge oder konstruktive Kritik.
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(); // doesn't 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();
});
}
});
};
Rufen Sie so auf:
rmRasync('/path/to/some/file/or/dir', function (err) {
if (err) return console.error('Could not rm', err);
// else success
});
Überraschend ausführliche und schlechte Antworten hier ...
So löschen Sie ein nicht leeres Verzeichnis auf den meisten Systemen:
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
});
Dies funktioniert unter MacOS und Linux, unter einigen Windows-Betriebssystemen jedoch möglicherweise nicht.
rimraf
stattdessen eine getestete Bibliothek wie .
rimraf
hat dieses Problem nicht, weil es die Shell nicht verwendet. Außerdem wird im Allgemeinen eine gut gepflegte Bibliothek aktualisiert, wenn weitere Probleme auftreten, und ein einmaliger Code wie dieser wird dies nicht tun.
dir
Variablen akzeptiert, kann Ihr Server kompromittiert werden. Wenn jemand anderes diesen Code an einen Ort kopiert, an dem Benutzereingaben in die dir
Variable akzeptiert werden, kann der Server kompromittiert werden.