Remover diretório que não está vazio


300

No meu aplicativo Nó, preciso remover um diretório que possui alguns arquivos, mas fs.rmdir funciona apenas em diretórios vazios. Como posso fazer isso?


1
Em resumo: fs.readdir(dirPath)para uma variedade de caminhos em uma pasta, repita fs.unlink(filename)para excluir cada arquivo e, finalmente, fs.rmdir(dirPath)para excluir a pasta agora vazia. Se você precisar se recuperar, verifique fs.lstat(filename).isDirectory().
Iono

Respostas:


319

Existe um módulo para isso chamado rimraf( https://npmjs.org/package/rimraf ). Ele fornece a mesma funcionalidade querm -Rf

Uso assíncrono :

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

Uso da sincronização :

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

1
Estranho, nunca vi um comportamento assim. Sugiro procurar e / ou registrar um bug. github.com/isaacs/rimraf/issues
Morgan ARR Allen

35
Isso pode ser feito facilmente com as bibliotecas do NodeJS Core. Por que instalar um pacote de terceiros não mantido?
SudoKid

4
@EmettSpeer Quando você quer dizer "ser feito facilmente"? Auto-escrever uma função como deleteFolderRecursivena resposta a seguir?
Freewind

23
"mas mesmo com a função abaixo, é melhor adicionar um pacote desnecessário ao seu sistema." Eu discordo fortemente. Você está reinventando a roda pela 19ª milionésima vez, sem absolutamente nenhuma razão e correndo o risco de introduzir bugs ou vulnerabilidades de segurança no processo. No mínimo, é uma perda de tempo. Inb4 "e se eles soltarem o pacote": no caso extremamente improvável de o pacote ser removido do registro npm, você sempre poderá substituí-lo pelo seu próprio . Não faz sentido enfaixar a cabeça antes que você a quebre.
Demonblack

3
agora você pode usar uma recursiveopção: stackoverflow.com/a/57866165/6269864

245

Para remover a pasta de forma síncrona

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
Talvez queira adicionar algumas verificações de que você não executará isso acidentalmente em '/'. Por exemplo, receber um caminho vazio e um erro de digitação no arquivo pode resultar em curPath como o diretório raiz.
26416 Jake_Howard

10
Implementação mais robusta: substitua var curPath = path + "/" + file;por, var curPath = p.join(path, file);desde que você inclua o módulo path:var p = require("path")
Andry

9
O Windows tem \ barras, por isso path.join(dirpath, file)deve ser melhor quepath + "/" + file
thybzi 28/02

5
Você pode obter "Tamanho máximo da pilha de chamadas excedido" com este código devido a muitas operações em um tempo de tick. @Walf Se você executa o aplicativo de console, você tem 1 cliente, não mais. Assim, não há necessidade de usar assíncrona para console app neste caso
Leonid Dashko

4
Eu recebo 'Erro: ENOTEMPTY: o diretório não está vazio'
Seagull

168

A maioria das pessoas que usam fs Node.js gostaria de funções próximas à "maneira Unix" de lidar com arquivos. Estou usando o fs-extra para trazer todas as coisas legais:

O fs-extra contém métodos que não estão incluídos no pacote fs do vanilla Node.js. Como mkdir -p, cp -r e rm -rf.

Melhor ainda, o fs-extra é uma queda no substituto do fs nativo. Todos os métodos em fs não são modificados e estão anexados a ele. Isso significa que você pode substituir fs por fs-extra :

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

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

E então você pode remover uma pasta da seguinte maneira:

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

para a versão de sincronização que você precisaremoveSync('/tmp/myFolder')
olidem

148

A partir de 2019 ...

A partir de Node.js 12.10.0 , fs.rmdirSyncsuporta um recursiveopções, para que possa finalmente fazer:

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

Onde a recursiveopção exclui o diretório inteiro recursivamente.


5
@anneb Isso acontece se você estiver usando uma versão mais antiga do Node.js (<12.10). A versão mais recente reconhece a opção recursive: truee exclui pastas não vazias sem reclamação.
GOTO 0

9
A remoção recursiva ainda é experimental a partir do nó v13.0.1
Tim

5
A assinatura da função é realmente fs.rmdir(path[, options], callback)oufs.rmdirSync(path[, options])
conceptdeluxe 27/11/19

@ Tim O que você quer dizer com experimental?
Emerica 18/04

2
@Emerica Nos documentos oficiais do node.js., existe uma grande notificação laranja dizendo que fs.rmdiré experimental com estabilidade 1. "Estabilidade: 1 - Experimental. O recurso não está sujeito às regras de versão semântica. Alterações ou remoção não compatíveis com versões anteriores podem ocorrer em qualquer versão futura. O uso do recurso não é recomendado em ambientes de produção ".
Tim

24

Minha resposta modificada de @oconnecp ( https://stackoverflow.com/a/25069828/3027390 )

Usa path.join para uma melhor experiência entre plataformas. Portanto, não esqueça de exigir.

var path = require('path');

Também renomeada função para 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

Normalmente, eu não ressuscito tópicos antigos, mas há muita rotatividade aqui e sem a resposta do rimraf, tudo isso parece muito complicado para mim.

Primeiro no Nó moderno (> = v8.0.0), você pode simplificar o processo usando apenas módulos principais do nó, totalmente assíncronos e paralelizar a desvinculação de arquivos simultaneamente, tudo em uma função de cinco linhas e ainda manter a legibilidade:

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

Em outra nota, um protetor para ataques de travessia de caminho é inadequado para essa função porque

  1. Está fora do escopo com base no Princípio da Responsabilidade Única .
  2. Deve ser manuseado pelo chamador, não esta função. Isso é semelhante à linha de comando, rm -rfpois é necessário um argumento e permitirá que o usuário rm -rf /solicite. Seria responsabilidade de um script não proteger o rmpróprio programa.
  3. Essa função seria incapaz de determinar um ataque, pois não possui um quadro de referência. Novamente, essa é a responsabilidade do chamador que teria o contexto de intenção que forneceria uma referência para comparar o percurso do caminho.
  4. Sym-links não são uma preocupação, como .isDirectory()é falsepara sym-links e não são desvinculados recursed em.

Por último, mas não menos importante, há uma rara condição de corrida em que a recursão poderá ocorrer erro se uma das entradas for desvinculada ou excluída fora deste script no momento certo enquanto essa recursão estiver em execução. Como esse cenário não é típico na maioria dos ambientes, provavelmente pode ser esquecido. No entanto, se necessário (para alguns casos extremos), esse problema pode ser mitigado com este exemplo um pouco mais complexo:

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

EDIT: Faça isDirectory()uma função. Remova o diretório real no final. Corrija a recursão ausente.


1
Esta é uma solução realmente elegante. Pergunta sobre o segundo exemplo de código: você não chama o awaitseu Promise.all(…); isso é intencional? Parece que em seu estado atual results.forEachiteraria sobre as promessas, enquanto o código espera iterar sobre os resultados. Estou esquecendo de algo?
Anton Strogonoff

@ Tony você está correto, é um erro de digitação / erro. Boa pegada!
Sukima 02/07/19

Talvez uma verificação primeiro para garantir que o diretório exista? algo comoif (!fs.existsSync(dir)) return
GTPV 10/07/19

@GTPV Por quê? Isso aumenta a responsabilidade desta função. readdirirá lançar um erro como deveria. Se você rmdir non-existing-diro código de saída for um erro. Seria responsabilidade do consumidor tentar / capturar. Este é o mesmo método descrito na documentação do Nó quando se trata de usar funções fs. Eles esperam que você tente capturar e observe os erros codepara determinar o que fazer. Um cheque extra introduz uma condição de corrida.
Sukima 10/07/19

Definitivamente, entendo o seu ponto. Embora eu esperasse intuitivamente, a tentativa de excluir uma pasta que não existe seria bem-sucedida, pois simplesmente não faria nada. Nenhuma condição de corrida se a versão síncrona de fs.existsfor usada. PS, esta é uma ótima solução.
GTPV 10/07/19

12

Aqui está uma versão assíncrona da resposta do @ 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

Eu escrevi essa função chamada remover pasta. Ele removerá recursivamente todos os arquivos e pastas em um local. O único pacote necessário é assíncrono.

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
A idéia é realmente não escrever seu próprio código se já tiver sido escrito por outra pessoa. A melhor maneira de fazer isso é usar rimraf ou fs-extra ou qualquer outro módulo de nó, para fazer o trabalho para você.
Victor Pudeyev

90
Sim, escrever seu próprio código é terrível, porque o uso de dezenas de módulos de terceiros para operações relativamente triviais nunca provou ter quaisquer inconvenientes em aplicativos de grande escala.
Eric

8

Se você estiver usando o nó 8+ deseja assincronicidade e não deseja dependências externas, aqui está a versão assíncrona / aguardada:

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

Versão assíncrona da resposta do @ SharpCoder usando fs.promises:

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

Eu cheguei aqui enquanto tentava acabar com o gulpe eu estou escrevendo para mais alcance.

Quando você deseja excluir arquivos e pastas usando del, você deve anexar /**para exclusão recursiva.

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

2

O pacote de fato é rimraf, mas aqui está minha pequena versão assíncrona:

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

De acordo com a fsdocumentação , fsPromisesatualmente fornece a recursiveopção experimentalmente, que, pelo menos no meu caso no Windows, remove o diretório e quaisquer arquivos nele.

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

Os recursive: truearquivos são removidos no Linux e MacOS?


1

Ultra velocidade e à prova de falhas

Você pode usar o lignatorpacote ( https://www.npmjs.com/package/lignator ), é mais rápido que qualquer código assíncrono (por exemplo, rimraf) e mais à prova de falhas (especialmente no Windows, onde a remoção de arquivos não é instantânea e os arquivos podem ser bloqueado por outros processos).

4,36 GB de dados, 28 042 arquivos, 4 217 pastas no Windows removidas em 15 segundos contra os 60 segundos de rimraf no disco rígido antigo.

const lignator = require('lignator');

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

1

A pasta de sincronização é removida com os arquivos ou apenas com um arquivo.

Eu não sou muito doador nem colaborador, mas não consegui encontrar uma boa solução para esse problema e tive que encontrar o meu caminho ... espero que gostem :)

Funciona perfeito para mim com qualquer número de diretórios e subdiretórios aninhados. Cuidado com o escopo 'this' ao repetir a função, sua implementação pode ser diferente. No meu caso, essa função permanece no retorno de outra função, por isso estou chamando isso com isso.

    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

Embora recursiveseja uma opção experimental defs.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

Atualização 2020

A partir da versão 12.10.0 recursiveOption foi adicionado para opções.

Observe que a exclusão recursiva é experimental .

Então você faria pela sincronização:

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

ou para assíncrono:

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

0

Basta usar o módulo rmdir ! é fácil e simples.


6
Nem sempre é uma boa ideia usar um módulo para cada pequeno pedaço de código. Se você tiver que criar um contrato de licença, por exemplo, isso gerará uma dor real.
Mijago

4
você precisa adicionar um código de exemplo para a sua resposta a ser mais interessante
Xeltor

0

Outra alternativa é usar o fs-promisemódulo que fornece versões promissificadas dos fs-extramódulos

você poderia escrever como este exemplo:

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)

note: o assíncrono / espera requer uma versão recente do nodejs (7.6+)


0

Uma maneira rápida e suja (talvez para teste) poderia ser usar diretamente o método execou spawnpara chamar a chamada do SO para remover o diretório. Leia mais em NodeJs child_process .

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

As desvantagens são:

  1. Você depende do sistema operacional subjacente, ou seja, o mesmo método seria executado no unix / linux, mas provavelmente não no Windows.
  2. Você não pode seqüestrar o processo sob condições ou erros. Você apenas entrega a tarefa ao SO subjacente e aguarda o retorno do código de saída.

Benefícios:

  1. Esses processos podem ser executados de forma assíncrona.
  2. Você pode ouvir a saída / erro do comando, portanto, a saída do comando não é perdida. Se a operação não for concluída, você poderá verificar o código de erro e tentar novamente.

2
Perfeito para quando você escreve um script e não deseja instalar dependências porque está prestes a excluir esse script em 30 segundos depois de excluir todos os seus arquivos!
Mathias

Sempre há maneiras de atrapalhar e excluir o sistema de arquivos raiz. Nesse caso, o OP pode remover o -fsinalizador para garantir a segurança ou ao digitar que ele / ela não excluirá tudo. exec + rmé um comando válido e útil no nó que eu uso frequentemente durante o teste.
Rash

0

Eu gostaria que houvesse uma maneira de fazer isso sem módulos adicionais para algo tão minúsculo e comum, mas isso é o melhor que eu poderia criar.

Atualização: agora deve funcionar no Windows (Windows 10 testado) e também nos sistemas 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);
    });
}

Talvez o fs-extra seja o caminho a seguir, se você quiser um único módulo.
b01

3
Este método é absolutamente perigoso. A concatenação de cadeias de caracteres de um comando do shell, especialmente sem escapar, convida vulnerabilidades de execução de código e similares. Se você usar o rmdir, use o child_process.execFileque não chama o shell e passe argumentos explicitamente.
Nevyn

@ Nevyn Vou tentar e atualizar minha resposta, se funcionar.
b01

Sempre prefira não usar terceiros! Obrigado!
Anton Mitsev 12/10

Além disso, esse método é bem lento. A API nativa do Nodejs é muito mais rápida.
mersey

0

Essa é uma abordagem usando o promisify e duas funções de ajuda (to e toAll) para resolver a promessa.

Ele executa todas as ações de forma assíncrona.

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

// sem uso de qualquer lib de terceiros

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
Isto irá funcionar para o que eu preciso, mas você pode querer usar o caminho ao invés de concatenar a barra: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
Eu acho que algo assim deve funcionar para diretórios aninhados.
fool4jesus

Sim, tanto para o diretório aninhado e um não-nested
Erisan Olasheni
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.