In meiner Node-Anwendung muss ich ein Verzeichnis entfernen, das einige Dateien enthält, aber fs.rmdir
funktioniert nur bei leeren Verzeichnissen. Wie kann ich das machen?
Antworten
Zu viele Anzeigen?Meine modifizierte Antwort von @oconnecp (https://stackoverflow.com/a/25069828/3027390)
Verwendet path.join für eine bessere plattformübergreifende Erfahrung. Also vergiss nicht, es zu requirieren.
var path = require('path');
Habe die Funktion auch in rimraf
umbenannt ;)
/**
* Verzeichnis rekursiv entfernen
* @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);
}
}
Von den Node-Dokumenten, wie man hier sehen kann.
Um ein Verhalten ähnlich dem Unix-Befehl
rm -rf
zu erhalten, verwenden Siefs.rm()
mit den Optionen{ recursive: true, force: true }
.
Zum Beispiel (ESM)
import { rm } from 'node:fs/promises';
await rm('/Pfad/zum', { recursive: true, force: true });
Ich belebe normalerweise alte Threads nicht wieder, aber hier gibt es viel zum Thema "Churn", und ohne die Antwort von rimraf erscheinen mir all diese Lösungen übermäßig kompliziert.
Zunächst können Sie in modernem Node (>= v8.0.0) den Prozess vereinfachen, indem Sie nur Node-Core-Module verwenden, vollständig asynchron sind und das Löschen von Dateien gleichzeitig parallelisieren, alles in einer Funktion von fünf Zeilen und dennoch die Lesbarkeit beibehalten:
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);
};
Zum anderen ist ein Schutz vor Pfad-Traversierungsangriffen für diese Funktion unangemessen, weil
- es außerhalb des Geltungsbereichs des Single Responsibility Principle liegt.
- von dem Aufrufer behandelt werden sollte nicht diese Funktion. Dies ist ähnlich wie der Befehlszeilenbefehl
rm -rf
in dem es ein Argument entgegennimmt und dem Benutzer erlaubt,rm -rf /
auszuführen, wenn danach gefragt wird. Es wäre die Verantwortung eines Skripts, nicht desrm
Programms selbst. - diese Funktion nicht in der Lage wäre, einen solchen Angriff zu erkennen, da sie keinen Referenzrahmen hat. Wiederum liegt es in der Verantwortung des Aufrufers, der die Absicht des Kontextes hätte, was ihm einen Bezug zum Vergleich der Pfadtraversierung geben würde.
- Symlinks sind kein Problem, da
.isDirectory()
false
für Symlinks ist und diese nicht rekursiv durchlaufen werden.
Zu guter Letzt gibt es eine seltene Race Condition, bei der die Rekursion einen Fehler zurückgeben könnte, wenn eines der Einträge außerhalb dieses Skripts zum richtigen Zeitpunkt gelöscht wurde, während diese Rekursion läuft. Da dieses Szenario in den meisten Umgebungen nicht typisch ist, kann es wahrscheinlich übersehen werden. Wenn jedoch erforderlich (für einige Randfälle), kann dieses Problem mit diesem etwas komplexeren Beispiel gemildert werden:
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 => {
// Ignoriere fehlende Dateien/Verzeichnisse; breche bei anderen Fehlern ab
if (result && result.error.code !== 'ENOENT') throw result.error;
});
await rmdir(dir);
};
EDIT: Mache isDirectory()
zu einer Funktion. Lösche das tatsächliche Verzeichnis am Ende. Behebe fehlende Rekursion.
Hier ist eine asynchrone Version von @SharpCoder's Antwort
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);
});
});
});
};
Ich habe diese Funktion namens "removeFolder" geschrieben. Sie entfernt rekursiv alle Dateien und Ordner an einem bestimmten Speicherort. Das einzige Paket, das dafür erforderlich ist, ist async.
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)
})
})
})
}