空でないディレクトリを削除


300

私のノードアプリケーションでは、ファイルがいくつかあるディレクトリを削除する必要がありますが、fs.rmdir空のディレクトリでしか機能しません。これどうやってするの?


1
つまりfs.readdir(dirPath)、フォルダー内のパスの配列の場合、反復しfs.unlink(filename)て各ファイルfs.rmdir(dirPath)を削除し、最後に空になったフォルダーを削除します。再帰する必要がある場合は、を確認してくださいfs.lstat(filename).isDirectory()
iono

回答:


319

このためのモジュールがありますrimrafhttps://npmjs.org/package/rimraf)。と同じ機能を提供しますrm -Rf

非同期の使用:

var rimraf = require("rimraf");
rimraf("/some/directory", function () { console.log("done"); });

同期の使用:

rimraf.sync("/some/directory");

1
奇妙なことに、私はそのような行動を見たことがありません。バグを検索または報告することをお勧めします。github.com/isaacs/rimraf/issues
Morgan ARRアレン

35
これはNodeJSコアライブラリで簡単に実行できるものであり、メンテナンスされていないサードパーティパッケージをインストールする理由は何ですか?
SudoKid

4
@EmettSpeer「簡単にできる」とはどういう意味ですか?deleteFolderRecursive次の答えのような関数を自己記述しますか?
Freewind

23
「しかし、機能がそれ以下の場合でも、システムに不要なパッケージを追加する必要があります。」私は強く同意しません。まったく理由もなく、1900万回目の車輪の再発明を行っており、その過程でバグやセキュリティの脆弱性を導入するリスクを冒しています。少なくとも、それは時間の無駄です。Inb4「パッケージがドロップされた場合」:パッケージがnpmレジストリから削除された非常にまれなイベントでは、いつでも独自のパッケージに置き換えることができます頭を折る前に包帯をしても意味がありません。
Demonblack 2018年

3
recursiveオプションを使用できるようになりました:stackoverflow.com/a/57866165/6269864

245

フォルダーを同期的に削除するには

const fs = require('fs');
const Path = require('path');

const deleteFolderRecursive = function(path) {
  if (fs.existsSync(path)) {
    fs.readdirSync(path).forEach((file, index) => {
      const curPath = Path.join(path, file);
      if (fs.lstatSync(curPath).isDirectory()) { // recurse
        deleteFolderRecursive(curPath);
      } else { // delete file
        fs.unlinkSync(curPath);
      }
    });
    fs.rmdirSync(path);
  }
};

33
「/」で誤ってこれを実行しないようにするために、いくつかのチェックを追加したい場合があります。たとえば、空のパスと入力ミスがファイルに渡されると、curPathがルートディレクトリになる可能性があります。
Jake_Howard

10
より堅牢な実装:置き換えvar curPath = path + "/" + file;var curPath = p.join(path, file);提供しますが、パスモジュールを含んでいた:var p = require("path")
Andryの

9
Windowsにはスラッシュが付いているので、path.join(dirpath, file)より優れているはずですpath + "/" + file
thybzi 2017

5
このコードを使用すると、1ティック時間での操作が多すぎるため、「最大コールスタックサイズを超えました」と表示されることがあります。@Walfコンソールアプリケーションを実行する場合、クライアントは1つで、それ以上はありません。この場合、コンソールアプリケーションの利用非同期にので、必要はありません
レオニードDashko

4
「エラー:ENOTEMPTY:ディレクトリが空ではありません」が表示される
Seagull

168

fsNode.jsを使用するほとんどの人は、ファイルを処理する「Unixの方法」に近い機能を望んでいます。私はすべてのクールなものをもたらすためにfs-extraを使用しています:

fs-extraには、通常のNode.js fsパッケージに含まれていないメソッドが含まれています。mkdir -p、cp -r、rm -rfなど。

さらに良いことに、fs-extraはネイティブfsに代わるものです。fsのすべてのメソッドは変更されておらず、それにアタッチされています。これは、fsをfs-extraで置き換えることができることを意味します。

// this can be replaced
const fs = require('fs')

// by this
const fs = require('fs-extra')

そして、あなたはこの方法でフォルダを削除することができます:

fs.removeSync('/tmp/myFolder'); 
//or
fs.remove('/tmp/myFolder', callback);

同期バージョンのためにあなたが必要とするremoveSync('/tmp/myFolder')
olidemを

148

2019年現在...

のとしてのNode.js 12.10.0fs.rmdirSyncサポートされていrecursiveますが、最終的に行うことができますので、オプション:

fs.rmdirSync(dir, { recursive: true });

このrecursiveオプションでは、ディレクトリ全体が再帰的に削除されます。


5
@annebこれは、古いバージョンのNode.js(<12.10)を使用している場合に発生します。最新バージョンはオプションrecursive: trueを認識し、空ではないフォルダーを問題なく削除します。
GOTO 0

9
ノードv13.0.1以降、再帰的な削除はまだ実験段階です
Tim

5
関数のシグネチャは実際にfs.rmdir(path[, options], callback)またはfs.rmdirSync(path[, options])
conceptdeluxe

@Tim実験的とはどういう意味ですか?
Emerica

2
@Emerica公式のnode.jsドキュメントでは、fs.rmdir安定性1で実験的であると言っている大きなオレンジ色の通知があります。将来のリリース。この機能を本番環境で使用することはお勧めしません。」
ティム

24

@oconnecpからの私の変更された回答(https://stackoverflow.com/a/25069828/3027390

クロスプラットフォームエクスペリエンスを向上させるために、path.joinを使用します。だから、それを要求することを忘れないでください。

var path = require('path');

関数の名前もrimraf;)に変更されました

/**
 * Remove directory recursively
 * @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);
    }
}

17

私は通常、古いスレッドを復活させることはしませんが、ここには多くのチャーンがあり、リムラフの回答を除き、これらはすべて私にとって複雑すぎるようです。

最初の最新のノード(> = v8.0.0)では、ノードコアモジュールのみを使用してプロセスを簡略化し、完全に非同期で、ファイルのリンク解除をすべて5行の関数で同時に並列化し、読みやすさを維持できます。

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);
};

別のメモでは、パストラバーサル攻撃のガードはこの機能には不適切です。

  1. 単一責任原則に基づく範囲外です。
  2. この関数ではなく、呼び出し元が処理する必要があります。これはrm -rf、引数を取り、rm -rf /要求された場合にユーザーに許可するという点で、コマンドラインに似ています。保護しないのはスクリプトの責任ですrmプログラム自体。
  3. この関数には参照フレームがないため、このような攻撃を特定できません。この場合も、パストラバーサルを比較するための参照を提供する意図のコンテキストを持つ呼び出し元の責任です。
  4. SYM-リンクが心配ではありません.isDirectory()ですfalseSYM-リンク用とに再帰ないリンクが解除されています。

最後に重要なことですが、この再帰の実行中に適切なタイミングでエントリの1つがこのスクリプトの外部でリンク解除または削除された場合、再帰がエラーになるというまれな競合状態があります。このシナリオはほとんどの環境では一般的ではないため、見落とされる可能性があります。ただし、必要に応じて(一部のエッジケースの場合)、この問題は、次の少し複雑な例で緩和できます。

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 => {
    // Ignore missing files/directories; bail on other errors
    if (result && result.error.code !== 'ENOENT') throw result.error;
  });
  await rmdir(dir);
};

編集:isDirectory()関数を作成します。最後に実際のディレクトリを削除します。欠落している再帰を修正します。


1
これは本当に素晴らしいソリューションです。2番目のコードサンプルに関する質問:を呼び出さないawaitでくださいPromise.all(…)。これは意図的ですか?現在の状態でresults.forEachは、コードが結果を反復することを期待している間、promiseを反復するようです。何か不足していますか?
アントンストロゴノフ

@トニーあなたは正しいですそれはタイプミス/バグです。良いキャッチ!
スキマスイッチ

多分最初にディレクトリが存在することを確認するためのチェックですか?次のようなものif (!fs.existsSync(dir)) return
GTPV

@GTPVなんで?これにより、この機能の責任が高まります。readdir必要に応じてエラーをスローします。あなたの場合はrmdir non-existing-dir終了コードは誤りです。消費するのは消費者の責任です。これは、fs関数の使用に関して、ノードのドキュメントで説明されている方法と同じです。彼らはあなたがトライ/キャッチしてエラーを見てcode何をすべきかを決定することを期待しています。追加のチェックにより、競合状態が発生します。
スキマスイッチ

私は間違いなくあなたの主張を理解しています。直感的には存在しないフォルダを削除しようとしても、何もしないので成功すると思います。の同期バージョンfs.existsが使用されている場合、競合状態は発生しません。PSこれは素晴らしいソリューションです。
GTPV

12

これは@SharpCoderの回答の非同期バージョンです

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);
            });
        });
    });
};

10

私は、フォルダーの削除と呼ばれるこの関数を書きました。場所内のすべてのファイルとフォルダを再帰的に削除します。必要な唯一のパッケージは非同期です。

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)
            })
        })
    })
}

4
自分のコードを他の誰かがすでに書いているのなら、そのアイデアは実際には書かないことです。それを行うためのより良い方法は、rimrafまたはfs-extraまたはその他のノードモジュールを使用して、作業を行うことです。
Victor Pudeyev 2014年

90
ええ、独自のコードを書くのはひどいです。何十ものサードパーティのモジュールを比較的簡単な操作に使用することは、大規模なアプリケーションに欠点があることが証明されていないからです。
エリック

8

ノード8+を使用していて非同期性が必要で、外部依存関係が不要な場合は、非同期/待機バージョンを次に示します。

const path = require('path');
const fs = require('fs');
const util = require('util');

const readdir = util.promisify(fs.readdir);
const lstat = util.promisify(fs.lstat);
const unlink = util.promisify(fs.unlink);
const rmdir = util.promisify(fs.rmdir);

const removeDir = async (dir) => {
    try {
        const files = await readdir(dir);
        await Promise.all(files.map(async (file) => {
            try {
                const p = path.join(dir, file);
                const stat = await lstat(p);
                if (stat.isDirectory()) {
                    await removeDir(p);
                } else {
                    await unlink(p);
                    console.log(`Removed file ${p}`);
                }
            } catch (err) {
                console.error(err);
            }
        }))
        await rmdir(dir);
        console.log(`Removed dir ${dir}`);
    } catch (err) {
      console.error(err);
    }
}

4

fs.promisesを使用した@SharpCoder の応答の非同期バージョン:

const fs = require('fs');
const afs = fs.promises;

const deleteFolderRecursive = async path =>  {
    if (fs.existsSync(path)) {
        for (let entry of await afs.readdir(path)) {
            const curPath = path + "/" + entry;
            if ((await afs.lstat(curPath)).isDirectory())
                await deleteFolderRecursive(curPath);
            else await afs.unlink(curPath);
        }
        await afs.rmdir(path);
    }
};

3

を乗り越えようとしているときにここに到達し、gulpさらに到達するために書いています。

を使用してファイルとフォルダを削除するdel場合は/**、再帰的に削除するために追加する必要があります。

gulp.task('clean', function () {
    return del(['some/path/to/delete/**']);
});

2

デファクトパッケージはですがrimraf、これが私の小さな非同期バージョンです。

const fs = require('fs')
const path = require('path')
const Q = require('q')

function rmdir (dir) {
  return Q.nfcall(fs.access, dir, fs.constants.W_OK)
    .then(() => {
      return Q.nfcall(fs.readdir, dir)
        .then(files => files.reduce((pre, f) => pre.then(() => {
          var sub = path.join(dir, f)
          return Q.nfcall(fs.lstat, sub).then(stat => {
            if (stat.isDirectory()) return rmdir(sub)
            return Q.nfcall(fs.unlink, sub)
          })
        }), Q()))
    })
    .then(() => Q.nfcall(fs.rmdir, dir))
}


2

fsドキュメントによると、fsPromises現在recursive、実験的なベースでオプションを提供しています。これは、少なくとも私のWindowsでは、ディレクトリとその中のファイルを削除します。

fsPromises.rmdir(path, {
  recursive: true
})

recursive: trueLinuxとMacOSの上のファイルを削除しますか?


1

超高速でフェイルプルーフ

lignatorパッケージ(https://www.npmjs.com/package/lignator)を使用できます。これは、非同期コード(rimrafなど)よりも高速で、フェイルプルーフ(特にWindowsではファイルの削除が瞬間的ではなく、ファイルが他のプロセスによってロックされる)。

古いHDDのrimrafの60秒に対して、4,36 GBのデータ、28 042ファイル、Windowsの4 217フォルダが15秒で削除されました。

const lignator = require('lignator');

lignator.remove('./build/');

1

同期フォルダーは、ファイルまたはファイルのみで削除します。

私は寄付者でも寄稿者でもありませんが、この問題の適切な解決策を見つけることができなかったので、自分の方法を見つけなければなり

ませんでした。ネストされたディレクトリとサブディレクトリ。関数を再帰するときの「this」のスコープに関する注意、実装は異なる場合があります。私の場合、この関数は別の関数の戻り値にとどまるので、これを使って呼び出しています。

    const fs = require('fs');

    deleteFileOrDir(path, pathTemp = false){
            if (fs.existsSync(path)) {
                if (fs.lstatSync(path).isDirectory()) {
                    var files = fs.readdirSync(path);
                    if (!files.length) return fs.rmdirSync(path);
                    for (var file in files) {
                        var currentPath = path + "/" + files[file];
                        if (!fs.existsSync(currentPath)) continue;
                        if (fs.lstatSync(currentPath).isFile()) {
                            fs.unlinkSync(currentPath);
                            continue;
                        }
                        if (fs.lstatSync(currentPath).isDirectory() && !fs.readdirSync(currentPath).length) {
                            fs.rmdirSync(currentPath);
                        } else {
                            this.deleteFileOrDir(currentPath, path);
                        }
                    }
                    this.deleteFileOrDir(path);
                } else {
                    fs.unlinkSync(path);
                }
            }
            if (pathTemp) this.deleteFileOrDir(pathTemp);
        }

1

一方でrecursiveの実験的なオプションでありますfs.rmdir

function rm (path, cb) {
    fs.stat(path, function (err, stats) {
        if (err)
            return cb(err);

        if (stats.isFile())
            return fs.unlink(path, cb);

        fs.rmdir(path, function (err) {
            if (!err || err && err.code != 'ENOTEMPTY') 
                return cb(err);

            fs.readdir(path, function (err, files) {
                if (err)
                    return cb(err);

                let next = i => i == files.length ? 
                    rm(path, cb) : 
                    rm(path + '/' + files[i], err => err ? cb(err) : next(i + 1));

                next(0);
            });
        });
    });
}

1

2020年の更新

バージョン12.10.0以降、オプションにrecursiveOptionが追加されました。

再帰的な削除は実験的なものであることに注意してください。

だからあなたは同期のために行うでしょう:

fs.rmdirSync(dir, {recursive: true});

または非同期の場合:

fs.rmdir(dir, {recursive: true});

0

rmdirモジュールを使用してください!簡単でシンプルです。


6
小さなすべてのコードにモジュールを使用することが常に良いとは限りません。たとえば、ライセンス契約を作成する必要がある場合、これは本当の苦痛を生み出します。
ミハゴ2017

4
あなたの答えをもっと面白くするには、コードサンプルを追加する必要があります
Xeltor

0

別の代替策は、fs-promise約束されたバージョンを提供するモジュールを使用することですfs-extraのモジュールを

次の例のように書くことができます:

const { remove, mkdirp, writeFile, readFile } = require('fs-promise')
const { join, dirname } = require('path')

async function createAndRemove() {
  const content = 'Hello World!'
  const root = join(__dirname, 'foo')
  const file = join(root, 'bar', 'baz', 'hello.txt')

  await mkdirp(dirname(file))
  await writeFile(file, content)
  console.log(await readFile(file, 'utf-8'))
  await remove(join(__dirname, 'foo'))
}

createAndRemove().catch(console.error)

注:async / awaitには最新のnodejsバージョン(7.6以降)が必要です


0

迅速かつダーティーな方法(おそらくテスト用)は、execまたはspawnメソッドを直接使用してOS呼び出しを呼び出し、ディレクトリを削除することです。NodeJs child_processの詳細を読んでください

let exec = require('child_process').exec
exec('rm -Rf /tmp/*.zip', callback)

欠点は次のとおりです。

  1. 基盤となるOSに依存しています。つまり、同じメソッドがunix / linuxで実行されますが、おそらくWindowsでは実行されません。
  2. 条件やエラーによってプロセスをハイジャックすることはできません。基盤となるOSにタスクを割り当て、終了コードが返されるのを待つだけです。

利点:

  1. これらのプロセスは非同期で実行できます。
  2. コマンドの出力/エラーをリッスンできるため、コマンド出力は失われません。操作が完了していない場合は、エラーコードを確認して再試行できます。

2
すべてのファイルを削除してから30秒後にこのスクリプトを削除しようとしているため、スクリプトを記述して依存関係をインストールしたくない場合に最適です。
Mathias

ルートファイルシステムを台無しにして削除する方法は常にあります。この場合、OPは-f安全のためにフラグを削除するか、入力中にすべてを削除しないことを確認できます。exec + rmテスト中によく使用するノードでの有効で便利なコマンドです。
発疹

0

非常に微小で一般的な何かのために追加のモジュールなしでこれを行う方法があったらいいのにと思いますが、これは私が思いつくことができる最高のものです。

更新:Windows(テスト済みのWindows 10)で動作するようになり、Linux / Unix / BSD / Macシステムでも動作するようになりました。

const
    execSync = require("child_process").execSync,
    fs = require("fs"),
    os = require("os");

let removeDirCmd, theDir;

removeDirCmd = os.platform() === 'win32' ? "rmdir /s /q " : "rm -rf ";

theDir = __dirname + "/../web-ui/css/";

// WARNING: Do not specify a single file as the windows rmdir command will error.
if (fs.existsSync(theDir)) {
    console.log(' removing the ' + theDir + ' directory.');
    execSync(removeDirCmd + '"' + theDir + '"', function (err) {
        console.log(err);
    });
}

たぶんfs-extraは、単一のモジュールが必要な場合に行く方法です。
b01

3
この方法は非常に危険です。シェルコマンドの文字列連結は、特にエスケープせずに、コード実行の脆弱性などを招きます。rmdirを使用する場合はchild_process.execFile、シェルを呼び出さないwhich を使用し、代わりに明示的に引数を渡します。
nevyn 2018年

@nevyn私はそれを試してみて、うまくいったら私の答えを更新します。
b01 2018年

常にサードパーティを使用しないことを好む!ありがとう!
アントン・ミツェフ2018年

それに加えて、この方法はかなり遅いです。NodejsネイティブAPIははるかに高速です。
マージー

0

これは、promisifyと2つのヘルプ関数(toとtoAll)を使用して約束を解決する1つの方法です。

非同期ですべてのアクションを実行します。

const fs = require('fs');
const { promisify } = require('util');
const to = require('./to');
const toAll = require('./toAll');

const readDirAsync = promisify(fs.readdir);
const rmDirAsync = promisify(fs.rmdir);
const unlinkAsync = promisify(fs.unlink);

/**
    * @author Aécio Levy
    * @function removeDirWithFiles
    * @usage: remove dir with files
    * @param {String} path
    */
const removeDirWithFiles = async path => {
    try {
        const file = readDirAsync(path);
        const [error, files] = await to(file);
        if (error) {
            throw new Error(error)
        }
        const arrayUnlink = files.map((fileName) => {
            return unlinkAsync(`${path}/${fileName}`);
        });
        const [errorUnlink, filesUnlink] = await toAll(arrayUnlink);
        if (errorUnlink) {
            throw new Error(errorUnlink);
        }
        const deleteDir = rmDirAsync(path);
        const [errorDelete, result] = await to(deleteDir);
        if (errorDelete) {
            throw new Error(errorDelete);
        }
    } catch (err) {
        console.log(err)
    }
}; 

0

//サードパーティのlibを使用しない

const fs = require('fs');
var FOLDER_PATH = "./dirname";
var files = fs.readdirSync(FOLDER_PATH);
files.forEach(element => {
    fs.unlinkSync(FOLDER_PATH + "/" + element);
});
fs.rmdirSync(FOLDER_PATH);

1
これは私が必要なもののために働くだろうが、あなたはスラッシュを連結するのではなく、パスを使用することがあります:fs.unllinkSync(path.join(FOLDER_PATH, element);
jackofallcode

-1
const fs = require("fs")
const path = require("path")

let _dirloc = '<path_do_the_directory>'

if (fs.existsSync(_dirloc)) {
  fs.readdir(path, (err, files) => {
    if (!err) {
      for (let file of files) {
        // Delete each file
        fs.unlinkSync(path.join(_dirloc, file))
      }
    }
  })
  // After the 'done' of each file delete,
  // Delete the directory itself.
  if (fs.unlinkSync(_dirloc)) {
    console.log('Directory has been deleted!')
  }
}

1
このようなものがネストされたディレクトリで機能するはずだと思います。
fool4jesus

はい、ネストされたディレクトリとネストされていないディレクトリの両方
Erisan Olasheni
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.