Tài liệu cho fs.rmdir rất ngắn và không giải thích hành vi của rmdir khi thư mục không trống.
H : Điều gì xảy ra nếu tôi cố gắng sử dụng API này để xóa một thư mục không trống?
Tài liệu cho fs.rmdir rất ngắn và không giải thích hành vi của rmdir khi thư mục không trống.
H : Điều gì xảy ra nếu tôi cố gắng sử dụng API này để xóa một thư mục không trống?
Câu trả lời:
Câu trả lời ngắn gọn: node.js fs.rmdir() gọi POSIX rmdir(); điều này sẽ xóa một thư mục trống hoặc trả về một lỗi . Trong trường hợp đã cho, lệnh gọi sẽ gọi hàm gọi lại và chuyển lỗi như một ngoại lệ.
Vấn đề ở đây là tài liệu node.js đề cập đến POSIX :
Các Node.js API Documents File System API bắt đầu ra như
trình bao bọc đơn giản xung quanh các chức năng POSIX tiêu chuẩn.
Điều này gần như thay đổi câu hỏi thành một bản sao: Có danh sách các hàm / API POSIX không?
Mô tả cho fs.rmdirlà ngắn gọn, nhưng đủ.
Rmdir không đồng bộ (2).
Đây rmdir(2)là một tham chiếu ngầm đến tài liệu cho rmdir() system call. Số (2) ở đây là quy ước trang unix man cũ để chỉ Phần 2 của các trang Thủ công, chứa các giao diện hạt nhân.
rmdirđệ quy: stackoverflow.com/a/57866165/6269864
Mặc dù sử dụng thư viện của bên thứ ba cho một thứ như vậy, tôi không thể nghĩ ra một giải pháp thanh lịch hơn. Vì vậy, tôi đã kết thúc bằng cách sử dụng rimraf npm -module .
Cài đặt nó
npm install rimraf
Hoặc cài đặt nó và lưu vào 'package.json' (có thể tìm thấy các tùy chọn lưu khác trong tài liệu npm-install )
npm install --save rimraf
Sau đó, bạn có thể làm như sau:
rmdir = require('rimraf');
rmdir('some/directory/with/files', function(error){});
Hoặc trong Coffeescript:
rmdir = require 'rimraf'
rmdir 'some/directory/with/files', (error)->
require('./node_modules/rimraf');- bạn có thể chỉrequire('rimraf');
fs.rmdirhỗ trợ recursivetùy chọn kể từ Node.js v12.10.0. Hãy xem câu trả lời của tôi .
Tôi đã viết về vấn đề này một cách chính xác .
Giải pháp trước đây của tôi dưới đây, mặc dù đơn giản, nhưng không được ưa thích. Chức năng sau đây, là một giải pháp đồng bộ; trong khi async có thể được ưu tiên hơn.
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);
}
};
[Chỉnh sửa] Đã thêm lstat thay vì stat để ngăn lỗi trên các liên kết tượng trưng
[Giải pháp trước đó]
Giải pháp của tôi cho điều này khá dễ thực hiện.
var exec = require('child_process').exec,child;
child = exec('rm -rf test',function(err,out) {
console.log(out); err && console.log(err);
});
Điều này được thu gọn cho trang này, nhưng ý tưởng cơ bản là đơn giản; thực hiện 'rm -r' trên dòng lệnh. Nếu ứng dụng của bạn cần chạy trên các loại hệ điều hành khác nhau, hãy đặt điều này vào một hàm và có nút chuyển if / else / để xử lý nó.
Bạn sẽ muốn xử lý tất cả các phản hồi; nhưng ý tưởng là đủ đơn giản.
fs.rmdirvà nó cũng sẽ không hoạt động trên Windows.
stat()khi nó nên được sử dụng lstat(), điều đó cũng có nghĩa là nó đã theo liên kết và xóa nội dung của thư mục đích trước khi nó bị lỗi! Eek! Tôi đã đề xuất một chỉnh sửa để thay đổi nó thành lstat.
path.join(path, file)thay vìpath + "/" + file
Node.js v12.10.0 giới thiệu recursivetùy chọn vào fs.rmdir. Vì fs.mkdirhỗ trợ cùng một tùy chọn kể từ v10.12.0, cả việc tạo và xóa thư mục đều có thể được thực thi đệ quy.
$ 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
Chỉ là một dấu chấm nhỏ trong số rất nhiều câu trả lời này, nhưng tôi nghĩ rằng thật tốt nếu chỉ ra nó.
Cá nhân (và nói chung) tôi muốn sử dụng một thư viện đã có sẵn, nếu có, để thực hiện nhiệm vụ. Lấy một thứ đã tồn tại có nghĩa là, đối với tôi và đặc biệt là trong thế giới nguồn mở, sử dụng và cải thiện một thứ đã tồn tại, điều này có thể mang lại kết quả tốt hơn là tự mình làm (Tôi đang cải thiện thứ mà người khác có làm xong).
Trong trường hợp này, với một tìm kiếm nhỏ, tôi đã phát hiện ra mô-đun fs-extra , nhằm mục đích thay thế cũng rimrafnhư câu trả lời cho nhu cầu xóa các thư mục đệ quy (dường như với các phiên bản không đồng bộ và đồng bộ hóa). Hơn nữa, nó đã có rất nhiều ngôi sao trên github và dường như hiện đang được chú ý: hai điều kiện này, ngoài thực tế là câu trả lời cho nhu cầu, làm cho nó trở thành con đường đi (gần như là một chút) đối với tôi.
Sử dụng child_process.execFile nó nhanh hơn .
child_process.execFile tương tự như child_process.exec () ngoại trừ nó * không thực thi một vỏ con mà thay vào đó là trực tiếp tệp được chỉ định.
Những công việc này. Bắt chướcrm -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']);
Chỉnh sửa : Tôi phải thừa nhận đây không phải là nền tảng chéo, sẽ không hoạt động trên Windows
Đây là một phiên bản đệ quy không đồng bộ hoạt động với các hứa hẹn. Tôi sử dụng thư viện 'Q' nhưng bất kỳ ai cũng sẽ thực hiện với một vài thay đổi (ví dụ: chức năng 'fail').
Để sử dụng nó, chúng ta phải tạo một vài trình bao bọc đơn giản xung quanh một số hàm Node cốt lõi, cụ thể là fs.stat, fs.readdir, fs.unlink và fs.rmdir để làm cho chúng thân thiện với hứa hẹn.
Họ đây rồi:
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;
}
Vì vậy, đây là hàm rm đệ quy:
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"...)?
Hình dung rằng đây là một lý do tốt để đi sâu vào nguồn;)
Từ những gì tôi có thể nói, fs.rmdirđược liên kết với hàm rmdir từ unistd.h. Từ trang người đàn ông POSIX cho rmdir :
Hàm rmdir () sẽ xóa một thư mục có tên được đặt theo đường dẫn. Thư mục sẽ bị xóa chỉ khi nó là một thư mục trống.
Nếu thư mục không phải là thư mục trống, rmdir () sẽ không thành công và đặt errno thành [EEXIST] hoặc [ENOTEMPTY].
Ngoài các câu trả lời "không" chính xác, gói rimraf cung cấp chức năng xóa đệ quy. Nó bắt chước rm -rf. Nó cũng được đóng gói chính thức bởi Ubuntu.
Tôi nhận ra rằng đây không phải là câu trả lời chính xác cho câu hỏi hiện tại, nhưng tôi nghĩ rằng điều này có thể hữu ích cho ai đó đang tìm kiếm ở đây trong tương lai (nó sẽ là với tôi!): Tôi đã tạo một đoạn mã nhỏ cho phép người ta chỉ xóa một cách đệ quy các thư mục . Nếu một thư mục (hoặc bất kỳ thư mục con nào của nó) có nội dung bên trong nó, nó sẽ được để riêng:
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;
};
hầu hết các ví dụ tôi thấy đều có các triển khai đồng bộ của việc xóa đệ quy cấu trúc thư mục trong nút.
Tôi cũng đã thấy một vài cái không đồng bộ không thực sự hoạt động tốt.
Tôi đã viết và sử dụng một tài khoản hoàn toàn không đồng bộ: https://gist.github.com/yoavniran/adbbe12ddf7978e070c0
Hàm này sẽ xóa một cách đệ quy một thư mục hoặc tệp mà bạn chỉ định một cách đồng bộ:
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);
}
}
Tôi chưa kiểm tra hành vi của chức năng này nếu:
Đệ quy xóa thư mục cho Node.js
Nó chỉ ra rằng mô-đun Node.js fs không có một phương pháp để loại bỏ thư mục và nội dung của nó một cách đệ quy. Thay vào đó, bạn nên đi qua cấu trúc thư mục và loại bỏ các mục nguyên tử, tức là các tệp riêng lẻ và thư mục trống. Vì vậy, tôi đã tìm thấy một ý chính hay của Takuo Kihira tại https://gist.github.com/2367067 được làm bằng JavaScript và quyết định tạo phiên bản CoffeeScript của nó:
đã cố gắng làm cho nó không an toàn vì việc xóa đồng bộ hóa sẽ gây ra lỗi nếu tệp hoặc dir đang được sử dụng tại thời điểm đó.
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();
}
});
Đây là chức năng nguyên mẫu tập lệnh cà phê mà tôi đã tạo cho fluode để xóa một thư mục một cách đệ quy
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()
đây là bài kiểm tra:
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
Một phiên bản đồng bộ gọn gàng của 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)
}
Và một IO song song, phiên bản không đồng bộ của rmdir. (nhanh hơn)
/**
* 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)
})
}
Dù sao, nếu bạn muốn IO tuần tự và gọi lại được gọi chính xác một lần (thành công hoặc gặp lỗi đầu tiên). Thay thế rmdir này bằng ở trên. (Chậm hơn)
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)
})
}
Tất cả chúng CHỈ phụ thuộc vào node.js và phải có tính di động.
Bài đăng này đã nhận được câu trả lời hàng đầu từ google nhưng không có câu trả lời nào đưa ra giải pháp:
không sử dụng các chức năng đồng bộ hóa
không yêu cầu thư viện bên ngoài
không sử dụng bash trực tiếp
Đây là asyncgiải pháp của tôi không giả định bất kỳ điều gì khác ngoài nút được cài đặt:
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);
});
}); } }
Theo dõi câu trả lời của @ geedew.
Đây là một triển khai không đồng bộ của rm -r(nghĩa là bạn có thể chuyển một đường dẫn đến một tệp hoặc thư mục). Tôi không phải là nhà phát triển nodejs có kinh nghiệm và đánh giá cao bất kỳ đề xuất hoặc lời phê bình mang tính xây dựng nào.
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();
});
}
});
};
Gọi như vậy:
rmRasync('/path/to/some/file/or/dir', function (err) {
if (err) return console.error('Could not rm', err);
// else success
});
Câu trả lời dài dòng đáng ngạc nhiên và không hay ở đây ...
Để xóa một thư mục không trống trên hầu hết các hệ thống:
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
});
điều này sẽ hoạt động trên MacOS và Linux, nhưng nó có thể không hoạt động trên một số hệ điều hành Windows.
rimrafthay thế.
rimrafkhông có vấn đề này vì nó không sử dụng shell. Ngoài ra, nói chung, một thư viện được duy trì tốt sẽ được cập nhật nếu có bất kỳ vấn đề nào khác phát sinh, và mã một lần như thế này sẽ không.
dirbiến thì máy chủ của bạn đã sẵn sàng bị xâm phạm. Nếu ai đó sao chép mã này vào một nơi mà nó chấp nhận đầu vào của người dùng trong dirbiến, thì máy chủ của họ đã sẵn sàng bị xâm phạm.