fs.rmdirのドキュメントは非常に短く、ディレクトリが空でない場合の rmdir の動作について説明していません。
Q : この API を使用して空でないディレクトリを削除しようとするとどうなりますか?
以下の私の以前の解決策は単純ですが、好まれません。次の関数は同期ソリューションです。async が優先される場合があります。
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);
}
};
[編集]シンボリックリンクのエラーを防ぐために stat の代わりに lstat を追加
[以前の解決策]
これに対する私の解決策は、実装が非常に簡単です。
var exec = require('child_process').exec,child;
child = exec('rm -rf test',function(err,out) {
console.log(out); err && console.log(err);
});
このページではこれを簡略化していますが、基本的な考え方は単純です。コマンドラインで「rm -r」を実行します。アプリをさまざまな種類の OS で実行する必要がある場合は、これを関数に入れ、それを処理する if/else/switch を用意します。
すべての応答を処理する必要があります。しかし、アイデアは十分に単純です。
簡単な答え: node.js fs.rmdir()
は POSIX を呼び出しrmdir()
ます。これにより、空のディレクトリが削除されるか、エラーが返されます。特定のケースでは、コールはコールバック関数を呼び出し、エラーを例外として渡します。
ここでの問題は、node.js のドキュメントがPOSIXを参照していることです。
Node.js API Docs File System API は、
標準 POSIX 関数の単純なラッパー。
これは、質問を次の複製にほとんど変更します: Is there a list of the POSIX API / functions?
の説明fs.rmdir
は簡潔ですが、十分です。
非同期 rmdir(2)。
here は、のrmdir(2)
ドキュメントへの暗黙的な参照rmdir() system call
です。ここでの番号 (2) は、カーネル インターフェイスを含むマニュアル ページのセクション 2 を示す古い UNIX マニュアル ページの規則です。
Node.js v12.10.0 で にrecursive
オプションが導入されましたfs.rmdir
。v10.12.0fs.mkdir
から同じオプションをサポートしているため、ディレクトリの作成と削除の両方を再帰的に実行できます。
$ 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
これは私のために働いた
fs.rmdirSync(folderpath, {recursive: true});
編集 2021:
現在、v14 で次のように置き換えられているようです。
fs.rmSync('./output', {recursive: true, force: true});
fs.rmdir
再帰的ではありません。
代わりに、 readdirpのような再帰的な fs.readdir モジュールを使用して、すべてのファイルとディレクトリを見つけることができます。次に、すべてのファイルを削除し、続いてすべてのディレクトリを削除します。
さらに簡単な解決策については、rimrafをご覧ください。
より高速な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では機能しないことを認めなければなりません
これは、promise で動作する非同期再帰バージョンです。私は 'Q' ライブラリを使用していますが、いくつかの変更 ('fail' 関数など) は誰でもできます。
それを利用するには、いくつかのコア Node 関数、つまり fs.stat、fs.readdir、fs.unlink、fs.rmdir の周りにいくつかの単純なラッパーを作成して、promise フレンドリーにする必要があります。
どうぞ:
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;
}
これは当面の質問に正確に答えているわけではないことは承知していますが、これは将来ここを検索する人にとって役立つかもしれないと思います (それは私にとってだったでしょう!) :ディレクトリ. ディレクトリ (またはその子孫ディレクトリのいずれか) にコンテンツが含まれている場合、それはそのまま残されます。
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;
};
これは、ソースに飛び込むための良い言い訳だと思いました ;)
私が知る限りfs.rmdir
、unistd.h の rmdir 関数にバインドされています。rmdirの POSIX man ページから:
rmdir() 関数は、パスで指定された名前のディレクトリを削除します。ディレクトリは、空のディレクトリである場合にのみ削除されます。
ディレクトリが空のディレクトリでない場合、rmdir() は失敗し、errno を [EEXIST] または [ENOTEMPTY] に設定します。
正しい「いいえ」の答えに加えて、rimrafパッケージは再帰的な削除機能を提供します。それは模倣しrm -rf
ます。また、Ubuntu によって公式にパッケージ化されています。
これは、フォルダーを再帰的に削除する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
この関数は、指定したディレクトリまたはファイルを同期的に再帰的に削除します。
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);
}
}
次の場合、この関数の動作をテストしていません。
この投稿はGoogleからトップの回答を得ていましたが、どの回答も次のような解決策を提供していません:
同期機能を利用しない
外部ライブラリを必要としません
bash を直接使用しない
async
ノードがインストールされていること以外は何も想定していない私のソリューションは次のとおりです。
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);
});
}); } }
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 が必要な場合は、コールバックを 1 回だけ呼び出します (成功または最初のエラーが発生した場合)。この 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のみに依存しており、移植可能である必要があります。