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?

sachin
fonte
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");
Morgan ARR Allen
fonte
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);
  }
};
SharpCoder
fonte
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);
Pierre Maoui
fonte
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.

K - A toxicidade no SO está crescendo.
fonte
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);
    }
}
thybzi
fonte
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.

Sukima
fonte
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);
            });
        });
    });
};
Tony Brix
fonte
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)
            })
        })
    })
}
oconnecp
fonte
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);
    }
}
RonZ
fonte
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);
    }
};
Erro 404
fonte
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/**']);
});
Jin Kwon
fonte
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))
}
clarkttfu
fonte
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?

oldboy
fonte
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/');
HankMoody
fonte
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);
        }
MetaTron
fonte
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);
            });
        });
    });
}
Aikon Mogwai
fonte
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});
Giovanni Patruno
fonte
0

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

Aminovski
fonte
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+)

Max Fichtelmann
fonte
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.
Erupção cutânea
fonte
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);
    });
}
b01
fonte
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)
    }
}; 
Aecio Levy
fonte
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);
Amy
fonte
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!')
  }
}
Erisan Olasheni
fonte
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