Является ли node.js rmdir рекурсивным? Будет ли он работать с непустыми каталогами?

Документация для fs.rmdir очень короткая и не объясняет поведение rmdir, когда каталог не пуст.

Q: Что произойдет, если я попытаюсь использовать этот API для удаления непустого каталога?

Ответ 1

Краткий ответ: node.js fs.rmdir() вызывает POSIX rmdir(); это удалит пустой каталог или вернет ошибку. В данном случае вызов вызовет функцию обратного вызова и передаст ошибку как исключение.

Проблема здесь в том, что документация node.js ссылается на POSIX:

Вступление Node.js API Документы Файловаясистема гласит:

Файловый ввод-вывод обеспечивается простыми обертками вокруг стандартных функций POSIX.

Это почти превращает вопрос в дубликат: Есть ли список API/функций POSIX?

Описание для fs.rmdir является кратким, но достаточным.

Asynchronous rmdir(2).

rmdir(2) здесь является неявной ссылкой на документацию для rmdir() system call. Число (2) здесь - это старое соглашение о man-страницах Unix для обозначения Раздела 2 страниц Руководства, содержащего интерфейсы ядра.

Ответ 2

Хотя использование сторонней библиотеки для такой вещи я не мог придумать более элегантное решение. Поэтому я закончил использование npm-модуля rimraf.

Установите его

npm install rimraf

Или установите его и сохраните в 'package.json' (другие параметры сохранения можно найти в npm-install docs)

npm install --save rimraf

Затем вы можете сделать следующее:

rmdir = require('rimraf');
rmdir('some/directory/with/files', function(error){});

Или в Coffeescript:

rmdir = require 'rimraf'
rmdir 'some/directory/with/files', (error)->

Ответ 3

Я писал об этой проблеме ровно.

Мое предыдущее решение ниже, хотя и простое, не является предпочтительным. Следующая функция - это синхронное решение; в то время как асинхронный режим может быть предпочтительным.

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

[Изменить] Добавлен lstat вместо stat для предотвращения ошибок в символических ссылках

[Предыдущее решение]

Мое решение для этого довольно просто реализовать.

var exec = require('child_process').exec,child;
child = exec('rm -rf test',function(err,out) { 
  console.log(out); err && console.log(err); 
});

Это уменьшено для этой страницы, но основная идея проста; выполните 'rm -r' в командной строке. Если ваше приложение должно работать с различными типами ОС, поместите его в функцию и используйте для этого команду if/else/.

Вы хотите обработать все ответы; но идея достаточно проста.

Ответ 4

Просто небольшая точка среди этой группы ответов, но я думаю, что это хорошо, чтобы указать на это.

Лично (и вообще) я бы предпочел использовать уже существующую библиотеку, если она доступна, для выполнения задачи. Использование уже существующей вещи означает для меня, и особенно в мире с открытым исходным кодом, использование и совершенствование уже существующей вещи, которая может оказаться в лучшем результате, чем сама по себе (я улучшаю то, что есть у кого-то другого сделано).

В этом случае при небольшом поиске я обнаружил модуль fs-extra, который также должен быть заменен на rimraf и отвечает на необходимость для удаления рекурсивно каталогов (по-видимому, с асинхронными и синхронными версиями). Кроме того, у него есть большое количество звезд на github и, кажется, в настоящее время поддерживается: эти два условия, в дополнение к тому, что ответы на необходимость, делают это способ пойти (почти немного для меня) для меня.

Ответ 5

fs.rmdir не рекурсивный.

Вместо этого вы можете использовать рекурсивный модуль fs.readdir, например readdirp, чтобы найти все файлы и каталоги. Затем удалите все файлы, а затем все каталоги.

Для еще более простого решения посмотрите rimraf.

Ответ 6

В Node.js v12.10.0 добавлена опция recursive в fs.rmdir. Поскольку fs.mkdir поддерживает ту же опцию, начиная с v10.12.0, создание и удаление каталога может выполняться рекурсивно.

$ 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

Ответ 7

Использовать child_process.execFile быстрее.

Документы NodeJS:

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

Ответ 8

Вот асинхронная рекурсивная версия, которая работает с promises. Я использую библиотеку "Q", но каждый будет делать с несколькими изменениями (например, функция "fail" ).

Чтобы использовать его, мы должны сделать несколько простых оберток вокруг некоторых основных функций Node, а именно fs.stat, fs.readdir, fs.unlink и fs.rmdir, чтобы сделать их перспективными.

Вот они:

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

Ответ 9

Полагали, что это было хорошим предлогом для погружения в источник;)

Из того, что я могу сказать, fs.rmdir привязан к функции rmdir от unistd.h. На странице POSIX для rmdir:

Функция rmdir() должна удалить каталог, имя которого задается формулой дорожка. Каталог должен быть удален только в том случае, если он является пустым каталогом.

Если каталог не является пустым каталогом, rmdir() завершится с ошибкой и установите errno на [EEXIST] или [ENOTEMPTY].

Ответ 10

В дополнение к правильным "нет" ответам пакет rimraf обеспечивает рекурсивную функцию удаления. Он имитирует rm -rf. Он также официально упакован от Ubuntu.

Ответ 11

Я понимаю, что это не совсем отвечает на вопрос, но я думаю, что это может быть полезно для кого-то, кто ищет здесь в будущем (это было бы для меня!): я сделал небольшой фрагмент, который позволяет рекурсивно удалите только пустые каталоги. Если каталог (или любой из его потоковых каталогов) содержит контент внутри него, он остается один:

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

https://gist.github.com/azaslavsky/661020d437fa199e95ab

Ответ 12

В большинстве примеров, которые я вижу, существуют синхронные реализации рекурсивного удаления структуры папок в node.

Я также видел несколько асинхронных, которые действительно не работают хорошо.

Я написал и использовал один полностью асинхронный: https://gist.github.com/yoavniran/adbbe12ddf7978e070c0

Ответ 13

Эта функция будет рекурсивно удалять каталог или файл, который вы укажете, синхронно:

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

Я не тестировал это поведение функции, если:

  • элемент не существует, или
  • элемент не может быть удален (например, из-за проблемы с разрешениями).

Ответ 14

Рекурсивный каталог удаления для Node.js

Оказалось, что модуль Node.js fs не имеет метода для рекурсивного удаления каталога и его содержимого. Вместо этого вы должны пройти через структуру каталогов и удалить атомарные элементы, то есть отдельные файлы и пустые каталоги. Таким образом, я нашел красивый Takuo Kihira в https://gist.github.com/2367067, сделанный в JavaScript, и решил сделать его версию CoffeeScript:

Ответ 15

попытался сделать его безопасным, поскольку удаление синхронизации приведет к ошибке, если в это время используется файл или каталог.

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

Ответ 16

Вот прототип кофе script, который я создал для 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

Ответ 17

Чистая синхронная версия 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 и обратный вызов вызывались ровно один раз (либо успех, либо с первой встречной ошибкой). Замените этот 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 и должны быть переносимыми.

Ответ 18

var fs = require('fs');
fs.delR = function(dir){
    var s = fs.lstatSync(dir);
    if(s.isFile())
        fs.unlinkSync(dir);
    if(!s.isDirectory())
        return;

    var fileArr = fs.readdirSync(dir);
    for(f in fileArr)
        fs.delR(dir+'/'+fileArr[f]);

    fs.rmdirSync(dir);
}

Ответ 19

Это сообщение получало главный ответ от google, но ни один из ответов не дал решение, которое:

  • не использует функции синхронизации

  • не требует внешних библиотек

  • не использует bash напрямую

Вот мое решение async, которое не предполагает ничего, кроме node:

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

Ответ 20

Следуя ответу @geedew.

Вот асинхронная реализация rm -r (т.е. вы можете передать путь к файлу или каталогу). Я не опытный разработчик nodejs и ценю любые предложения или конструктивную критику.

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(); // does not 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();
            });
        }
    });
};

вызывать так:

rmRasync('/path/to/some/file/or/dir', function (err) {
    if (err) return console.error('Could not rm', err);
    // else success
});

Ответ 21

Удивительно многословные и плохие ответы здесь...

Чтобы удалить непустой каталог в большинстве систем:

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

это будет работать на MacOS и Linux, но может не работать на некоторых ОС Windows.