procura de diretório recursivo do node.js fs.readdir

268

Alguma idéia em um diretório assíncrono pesquisa usando fs.readdir? Sei que poderíamos introduzir recursão e chamar a função de diretório de leitura com o próximo diretório para ler, mas estou um pouco preocupado com o fato de não ser assíncrono ...

Alguma ideia? Eu olhei para o node-walk, o que é ótimo, mas não me fornece apenas os arquivos de uma matriz, como o readdir. Apesar

Procurando saída como ...

['file1.txt', 'file2.txt', 'dir/file3.txt']
rastejar
fonte

Respostas:

379

Existem basicamente duas maneiras de conseguir isso. Em um ambiente assíncrono, você notará que existem dois tipos de loops: serial e paralelo. Um loop serial espera que uma iteração seja concluída antes de passar para a próxima iteração - isso garante que todas as iterações do loop sejam concluídas em ordem. Em um loop paralelo, todas as iterações são iniciadas ao mesmo tempo e uma pode ser concluída antes da outra, no entanto, é muito mais rápida que um loop serial. Portanto, nesse caso, provavelmente é melhor usar um loop paralelo, porque não importa em que ordem a caminhada é concluída, desde que ela complete e retorne os resultados (a menos que você os queira em ordem).

Um loop paralelo ficaria assim:

var fs = require('fs');
var path = require('path');
var walk = function(dir, done) {
  var results = [];
  fs.readdir(dir, function(err, list) {
    if (err) return done(err);
    var pending = list.length;
    if (!pending) return done(null, results);
    list.forEach(function(file) {
      file = path.resolve(dir, file);
      fs.stat(file, function(err, stat) {
        if (stat && stat.isDirectory()) {
          walk(file, function(err, res) {
            results = results.concat(res);
            if (!--pending) done(null, results);
          });
        } else {
          results.push(file);
          if (!--pending) done(null, results);
        }
      });
    });
  });
};

Um loop serial ficaria assim:

var fs = require('fs');
var path = require('path');
var walk = function(dir, done) {
  var results = [];
  fs.readdir(dir, function(err, list) {
    if (err) return done(err);
    var i = 0;
    (function next() {
      var file = list[i++];
      if (!file) return done(null, results);
      file = path.resolve(dir, file);
      fs.stat(file, function(err, stat) {
        if (stat && stat.isDirectory()) {
          walk(file, function(err, res) {
            results = results.concat(res);
            next();
          });
        } else {
          results.push(file);
          next();
        }
      });
    })();
  });
};

E para testá-lo em seu diretório pessoal (AVISO: a lista de resultados será enorme se você tiver muitas coisas em seu diretório pessoal):

walk(process.env.HOME, function(err, results) {
  if (err) throw err;
  console.log(results);
});

EDIT: Exemplos aprimorados.

chjj
fonte
10
Cuidado, a resposta "loop paralelo" do chjj acima tem um erro nos casos em que uma pasta vazia é percorrida. A correção é: var pendente = list.length; if (! pendente) concluído (nulo, resultados); // adicione esta linha! list.forEach (function (file) {...
Vasil Daskalopoulos
27
file = dir + '/' + file;Isto não é recomendado. Você deve usar: var path = require('path'); file = path.resolve(dir, file);
Leiko
7
@onetrickpony porque se você usar path.resolve(...)você vai ter um caminho adequado se você está no Windows ou Unix :) O que significa que você vai obter algo como C:\\some\\foo\\pathno Windows e /some/foo/pathem sistemas Unix
Leiko
19
Fiz uma votação baixa porque sua resposta foi ótima quando você a escreveu pela primeira vez em 2011, mas em 2014 as pessoas usam módulos de código aberto e escrevem menos códigos por conta própria e contribuem para os módulos dos quais eles e tantas outras pessoas dependem. Por exemplo tentativa nó-dir para obter exatamente a saída exigida pela @crawf usando esta linha de código:require('node-dir').files(__dirname, function(err, files) { console.log(files); });
Christiaan Westerbeek
5
Para qualquer um que esteja confuso sobre a !--sintaxe, uma pergunta foi feita sobre isso #
Tas
146

Este usa a quantidade máxima de recursos novos e buzzwordy disponíveis no nó 8, incluindo Promessas, util / promisify, desestruturação, espera assíncrona, mapa + redução e muito mais, fazendo com que seus colegas de trabalho coçam a cabeça enquanto tentam descobrir o que está acontecendo.

Nó 8+

Sem dependências externas.

const { promisify } = require('util');
const { resolve } = require('path');
const fs = require('fs');
const readdir = promisify(fs.readdir);
const stat = promisify(fs.stat);

async function getFiles(dir) {
  const subdirs = await readdir(dir);
  const files = await Promise.all(subdirs.map(async (subdir) => {
    const res = resolve(dir, subdir);
    return (await stat(res)).isDirectory() ? getFiles(res) : res;
  }));
  return files.reduce((a, f) => a.concat(f), []);
}

Uso

getFiles(__dirname)
  .then(files => console.log(files))
  .catch(e => console.error(e));

Nó 10.10+

Atualizado para o nó 10+ com ainda mais whizbang:

const { resolve } = require('path');
const { readdir } = require('fs').promises;

async function getFiles(dir) {
  const dirents = await readdir(dir, { withFileTypes: true });
  const files = await Promise.all(dirents.map((dirent) => {
    const res = resolve(dir, dirent.name);
    return dirent.isDirectory() ? getFiles(res) : res;
  }));
  return Array.prototype.concat(...files);
}

Observe que a partir do nó 11.15.0 você pode usar em files.flat()vez de Array.prototype.concat(...files)achatar a matriz de arquivos.

Nó 11+

Se você quiser explodir completamente a cabeça de todos, use a versão a seguir usando iteradores assíncronos . Além de ser muito legal, também permite que os consumidores obtenham resultados um de cada vez, tornando-o mais adequado para diretórios realmente grandes.

const { resolve } = require('path');
const { readdir } = require('fs').promises;

async function* getFiles(dir) {
  const dirents = await readdir(dir, { withFileTypes: true });
  for (const dirent of dirents) {
    const res = resolve(dir, dirent.name);
    if (dirent.isDirectory()) {
      yield* getFiles(res);
    } else {
      yield res;
    }
  }
}

O uso mudou porque o tipo de retorno agora é um iterador assíncrono em vez de uma promessa

(async () => {
  for await (const f of getFiles('.')) {
    console.log(f);
  }
})()

Caso alguém esteja interessado, escrevi mais sobre iteradores assíncronos aqui: https://qwtel.com/posts/software/async-generators-in-the-wild/

qwtel
fonte
5
A nomeação de subdire subdirsé enganosa, pois esses podem ser realmente arquivos (sugiro algo como itemInDirou item_in_dirou simplesmente item). Mas essa solução parece mais limpa que a aceita e é muito menos código. Também não acho muito mais complicado que o código na resposta aceita. +1
Zelphir Kaltstahl
1
Você pode tornar isso ainda mais complicado usando require(fs).promisese simplesmente soltar util.promisifycompletamente. Pessoalmente, eu alias fs para fs.promises.
MushinNoShin
2
Podemos tornar isso mais rápido com uma pequena alteração: passar o segundo argumento para readdirAKA, como o objeto de opções, para readdir(dir, {withFileTypes: true})retornar todos os itens com suas informações de tipo, para que não precisemos ligar statpara obter as informações que readdiragora nos fornecem costas. Isso evita a necessidade de fazer chamadas sys adicionais. Detalhes aqui
cacoder 23/05/19
1
@ codificador Atualizado para incluir withFileTypes. Obrigado pela dica.
qwtel
no nó 10.10+, se você substituir return Array.prototype.concat(...files);por, let result = Array.prototype.concat(...files); return result.map(file => file.split('\\').join('/'));certifique-se de que os dirs retornem um "/" e não um "\". Se você não se importa com regex, você também pode fazerreturn result.map(file => file.replace(/\\/g, '/'));
SwiftNinjaPro
106

Apenas no caso de alguém achar útil, eu também montei uma versão síncrona .

var walk = function(dir) {
    var results = [];
    var list = fs.readdirSync(dir);
    list.forEach(function(file) {
        file = dir + '/' + file;
        var stat = fs.statSync(file);
        if (stat && stat.isDirectory()) { 
            /* Recurse into a subdirectory */
            results = results.concat(walk(file));
        } else { 
            /* Is a file */
            results.push(file);
        }
    });
    return results;
}

Dica: Para usar menos recursos ao filtrar. Filtre dentro dessa própria função. Por exemplo, substitua results.push(file);pelo código abaixo. Ajuste conforme necessário:

    file_type = file.split(".").pop();
    file_name = file.split(/(\\|\/)/g).pop();
    if (file_type == "json") results.push(file);
Victor Powell
fonte
60
Eu gosto dessa solução, exceto pela sua falta de ponto e vírgula!
MPEN
Isto é simples. Mas também um pouco ingênuo. Pode causar um stackoverflow se um diretório contiver um link para um diretório pai. Talvez use em lstatvez disso? Ou então, adicione uma verificação de recursividade para limitar o nível de recursividade.
conradkleinespel
14
Considere o uso de file = require ( "caminho") join (dir, arquivo).
mkamioner
16
@mpen Ponto-e-vírgula são redundantes
Ally
Isso também funciona melhor para mim. Embora eu também tenha adicionado um filtro para filtrar por uma extensão de arquivo específica.
187 Brian
87

A. Dê uma olhada no módulo de arquivo . Tem uma função chamada walk:

file.walk (início, retorno de chamada)

Navega em uma árvore de arquivos, chamando retorno de chamada para cada diretório, passando (nulo, dirPath, dirs, arquivos).

Isso pode ser para você! E sim, é assíncrono. No entanto, acho que você teria que agregar o caminho completo, se necessário.

B. Uma alternativa, e até uma das minhas favoritas: use o unix findpara isso. Por que fazer algo de novo, que já foi programado? Talvez não seja exatamente o que você precisa, mas ainda vale a pena conferir:

var execFile = require('child_process').execFile;
execFile('find', [ 'somepath/' ], function(err, stdout, stderr) {
  var file_list = stdout.split('\n');
  /* now you've got a list with full path file names */
});

O Find possui um bom mecanismo de armazenamento interno que torna as pesquisas subseqüentes muito rápidas, desde que apenas poucas pastas tenham sido alteradas.

Johann Philipp Strathausen
fonte
9
Isso é apenas UNIX?
Mohsen 23/07
Tinha uma pergunta sobre o exemplo B: Para execFile () (e exec ()), stderr e stdout são Buffers ... então você não precisaria fazer stdout.toString.split ("\ n"), pois Buffers não são Strings?
Cheruvim
8
bom, mas não multiplataforma.
F0ster 19/11/2015
A propósito: Não, A não é apenas o Unix! Somente B é apenas Unix. No entanto, o Windows 10 agora vem com um subsistema Linux. Então, mesmo B funcionaria no Windows atualmente.
Johann Philipp Strathausen
o WSL não precisaria ser ativado no PC dos usuários finais para que ele funcionasse no Windows?
Oldboy
38

Outro bom pacote npm é o glob .

npm install glob

É muito poderoso e deve cobrir todas as suas necessidades recorrentes.

Editar:

Na verdade, eu não estava perfeitamente feliz com a glob, então criei readdirp .

Estou muito confiante de que sua API facilita a localização de arquivos e diretórios de forma recursiva e a aplicação de filtros específicos.

Leia a documentação para ter uma idéia melhor do que ele faz e instale via:

npm install readdirp

Thorsten Lorenz
fonte
Melhor módulo na minha opinião. E é semelhante a muitos outros projetos, como Grunt, Mocha, etc. e outros 80.000 + outros projetos. Apenas dizendo.
Yanick Rochon
29

Eu recomendo usar o node-glob para realizar essa tarefa.

var glob = require( 'glob' );  

glob( 'dirname/**/*.js', function( err, files ) {
  console.log( files );
});
Diogo Cardoso
fonte
14

Se você deseja usar um pacote npm, a chave inglesa é muito boa.

var wrench = require("wrench");

var files = wrench.readdirSyncRecursive("directory");

wrench.readdirRecursive("directory", function (error, files) {
    // live your dreams
});

EDIT (2018):
qualquer pessoa que esteja lendo recentemente: o autor reprovou este pacote em 2015:

O wrench.js está obsoleto e não é atualizado há algum tempo. Eu recomendo o uso do fs-extra para realizar operações extras do sistema de arquivos.

Domenic
fonte
@ Domicic, como você faz denodifyisso? O retorno de chamada é disparado várias vezes (recursivamente). Portanto, o uso Q.denodify(wrench.readdirRecursive)retorna apenas o primeiro resultado.
Onur Yıldırım
1
@ OnurYıldırım sim, este não é um bom ajuste para promessas como estão. Você precisaria escrever algo que retorne várias promessas ou algo que aguarde até que todos os subdiretórios sejam enumerados antes de retornar uma promessa. Para o último, consulte github.com/kriskowal/q-io#listdirectorytreepath
Domenic
9

Adorei a resposta do chjj acima e não teria sido capaz de criar minha versão do loop paralelo sem esse início.

var fs = require("fs");

var tree = function(dir, done) {
  var results = {
        "path": dir
        ,"children": []
      };
  fs.readdir(dir, function(err, list) {
    if (err) { return done(err); }
    var pending = list.length;
    if (!pending) { return done(null, results); }
    list.forEach(function(file) {
      fs.stat(dir + '/' + file, function(err, stat) {
        if (stat && stat.isDirectory()) {
          tree(dir + '/' + file, function(err, res) {
            results.children.push(res);
            if (!--pending){ done(null, results); }
          });
        } else {
          results.children.push({"path": dir + "/" + file});
          if (!--pending) { done(null, results); }
        }
      });
    });
  });
};

module.exports = tree;

Eu criei um Gist também. Comentários bem-vindos. Ainda estou começando no domínio do NodeJS, e é dessa maneira que espero aprender mais.

kalisjoshua
fonte
9

Com recursão

var fs = require('fs')
var path = process.cwd()
var files = []

var getFiles = function(path, files){
    fs.readdirSync(path).forEach(function(file){
        var subpath = path + '/' + file;
        if(fs.lstatSync(subpath).isDirectory()){
            getFiles(subpath, files);
        } else {
            files.push(path + '/' + file);
        }
    });     
}

Chamando

getFiles(path, files)
console.log(files) // will log all files in directory
Loourr
fonte
3
Eu sugiro que não juntar as cordas de caminho com /mas utilizando o pathmódulo: path.join(searchPath, file). Dessa forma, você obterá os caminhos corretos, independentemente do sistema operacional.
Moritz Friedrich
8

Use node-dir para produzir exatamente a saída que você deseja

var dir = require('node-dir');

dir.files(__dirname, function(err, files) {
  if (err) throw err;
  console.log(files);
  //we have an array of files now, so now we can iterate that array
  files.forEach(function(path) {
    action(null, path);
  })
});
Christiaan Westerbeek
fonte
O node-dir estava funcionando bem, mas quando o usei com o webpack, tenho alguns problemas estranhos. Um  é inserido na função readFiles como em "if (err)  {" causando um erro "não detectado SyntaxError: token inesperado {". Estou perplexo por este problema e minha reação imediata é substituir nó-dir com algo semelhante
Parth
1
@Parth este comentário não vai lhe dar respostas. Escreva uma nova pergunta completa no SO ou crie um problema no repositório do GitHub. Quando você elaborar bem em sua pergunta, você pode até mesmo ser capaz de resolver o seu problema, mesmo sem ter que publicá-la
Christiaan Westerbeek
1
O comentário de @ Parth ainda pode ser um aviso útil para outras pessoas que estão considerando sua sugestão como a solução para o problema. Eles podem não ter sido procurando uma resposta nesta seção de comentários :)
4

Eu codifiquei isso recentemente e achei que faria sentido compartilhar isso aqui. O código faz uso da biblioteca assíncrona .

var fs = require('fs');
var async = require('async');

var scan = function(dir, suffix, callback) {
  fs.readdir(dir, function(err, files) {
    var returnFiles = [];
    async.each(files, function(file, next) {
      var filePath = dir + '/' + file;
      fs.stat(filePath, function(err, stat) {
        if (err) {
          return next(err);
        }
        if (stat.isDirectory()) {
          scan(filePath, suffix, function(err, results) {
            if (err) {
              return next(err);
            }
            returnFiles = returnFiles.concat(results);
            next();
          })
        }
        else if (stat.isFile()) {
          if (file.indexOf(suffix, file.length - suffix.length) !== -1) {
            returnFiles.push(filePath);
          }
          next();
        }
      });
    }, function(err) {
      callback(err, returnFiles);
    });
  });
};

Você pode usá-lo assim:

scan('/some/dir', '.ext', function(err, files) {
  // Do something with files that ends in '.ext'.
  console.log(files);
});
recidivo
fonte
2
Este. Isso é tão arrumado e simples de usar. Coloquei-o em um módulo, exigi-lo e funciona como um sanduíche mcdream.
Jay
4

Uma biblioteca chamada Filehound é outra opção. Ele procurará recursivamente um determinado diretório (diretório de trabalho por padrão). Ele suporta vários filtros, retornos de chamada, promessas e pesquisas de sincronização.

Por exemplo, pesquise no diretório de trabalho atual todos os arquivos (usando retornos de chamada):

const Filehound = require('filehound');

Filehound.create()
.find((err, files) => {
    if (err) {
        return console.error(`error: ${err}`);
    }
    console.log(files); // array of files
});

Ou promete e especificando um diretório específico:

const Filehound = require('filehound');

Filehound.create()
.paths("/tmp")
.find()
.each(console.log);

Consulte os documentos para obter mais casos de uso e exemplos de uso: https://github.com/nspragg/filehound

Disclaimer: Eu sou o autor.

nickool
fonte
4

Usando async / waitit, isso deve funcionar:

const FS = require('fs');
const readDir = promisify(FS.readdir);
const fileStat = promisify(FS.stat);

async function getFiles(dir) {
    let files = await readDir(dir);

    let result = files.map(file => {
        let path = Path.join(dir,file);
        return fileStat(path).then(stat => stat.isDirectory() ? getFiles(path) : path);
    });

    return flatten(await Promise.all(result));
}

function flatten(arr) {
    return Array.prototype.concat(...arr);
}

Você pode usar o bluebird.Promisify ou este:

/**
 * Returns a function that will wrap the given `nodeFunction`. Instead of taking a callback, the returned function will return a promise whose fate is decided by the callback behavior of the given node function. The node function should conform to node.js convention of accepting a callback as last argument and calling that callback with error as the first argument and success value on the second argument.
 *
 * @param {Function} nodeFunction
 * @returns {Function}
 */
module.exports = function promisify(nodeFunction) {
    return function(...args) {
        return new Promise((resolve, reject) => {
            nodeFunction.call(this, ...args, (err, data) => {
                if(err) {
                    reject(err);
                } else {
                    resolve(data);
                }
            })
        });
    };
};

O nó 8+ possui o Promisify embutido

Veja minha outra resposta para uma abordagem de gerador que pode fornecer resultados ainda mais rápidos.

mpen
fonte
4

Assíncrono

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

const readdir = (p, done, a = [], i = 0) => fs.readdir(p, (e, d = []) =>
  d.map(f => readdir(a[a.push(path.join(p, f)) - 1], () =>
    ++i == d.length && done(a), a)).length || done(a))

readdir(__dirname, console.log)

Sincronizar

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

const readdirSync = (p, a = []) => {
  if (fs.statSync(p).isDirectory())
    fs.readdirSync(p).map(f => readdirSync(a[a.push(path.join(p, f)) - 1], a))
  return a
}

console.log(readdirSync(__dirname))

Legível assíncrona

function readdir (currentPath, done, allFiles = [], i = 0) {
  fs.readdir(currentPath, function (e, directoryFiles = []) {
    if (!directoryFiles.length)
      return done(allFiles)
    directoryFiles.map(function (file) {
      var joinedPath = path.join(currentPath, file)
      allFiles.push(joinedPath)
      readdir(joinedPath, function () {
        i = i + 1
        if (i == directoryFiles.length)
          done(allFiles)}
      , allFiles)
    })
  })
}

readdir(__dirname, console.log)

Nota: ambas as versões seguirão links simbólicos (iguais ao original fs.readdir)

Afanasii Kurakin
fonte
3

Confira a biblioteca final-fs . Ele fornece uma readdirRecursivefunção:

ffs.readdirRecursive(dirPath, true, 'my/initial/path')
    .then(function (files) {
        // in the `files` variable you've got all the files
    })
    .otherwise(function (err) {
        // something went wrong
    });
Szymon Wygnański
fonte
2

Implementação de promessa autônoma

Estou usando a biblioteca when.js promessa neste exemplo.

var fs = require('fs')
, path = require('path')
, when = require('when')
, nodefn = require('when/node/function');

function walk (directory, includeDir) {
    var results = [];
    return when.map(nodefn.call(fs.readdir, directory), function(file) {
        file = path.join(directory, file);
        return nodefn.call(fs.stat, file).then(function(stat) {
            if (stat.isFile()) { return results.push(file); }
            if (includeDir) { results.push(file + path.sep); }
            return walk(file, includeDir).then(function(filesInDir) {
                results = results.concat(filesInDir);
            });
        });
    }).then(function() {
        return results;
    });
};

walk(__dirname).then(function(files) {
    console.log(files);
}).otherwise(function(error) {
    console.error(error.stack || error);
});

Incluí um parâmetro opcional includeDirque incluirá diretórios na lista de arquivos, se definido como true.

JayQuerie.com
fonte
1

Aqui está mais uma implementação. Nenhuma das soluções acima possui limitadores e, portanto, se sua estrutura de diretórios for grande, todas elas serão danificadas e eventualmente ficarão sem recursos.

var async = require('async');
var fs = require('fs');
var resolve = require('path').resolve;

var scan = function(path, concurrency, callback) {
    var list = [];

    var walker = async.queue(function(path, callback) {
        fs.stat(path, function(err, stats) {
            if (err) {
                return callback(err);
            } else {
                if (stats.isDirectory()) {
                    fs.readdir(path, function(err, files) {
                        if (err) {
                            callback(err);
                        } else {
                            for (var i = 0; i < files.length; i++) {
                                walker.push(resolve(path, files[i]));
                            }
                            callback();
                        }
                    });
                } else {
                    list.push(path);
                    callback();
                }
            }
        });
    }, concurrency);

    walker.push(path);

    walker.drain = function() {
        callback(list);
    }
};

Usar uma simultaneidade de 50 funciona muito bem e é quase tão rápido quanto as implementações mais simples para estruturas de diretório pequenas.

Boson do macaco
fonte
1

Modifiquei a resposta baseada em Promessas de Trevor Senior para trabalhar com o Bluebird

var fs = require('fs'),
    path = require('path'),
    Promise = require('bluebird');

var readdirAsync = Promise.promisify(fs.readdir);
var statAsync = Promise.promisify(fs.stat);
function walkFiles (directory) {
    var results = [];
    return readdirAsync(directory).map(function(file) {
        file = path.join(directory, file);
        return statAsync(file).then(function(stat) {
            if (stat.isFile()) {
                return results.push(file);
            }
            return walkFiles(file).then(function(filesInDir) {
                results = results.concat(filesInDir);
            });
        });
    }).then(function() {
        return results;
    });
}

//use
walkDir(__dirname).then(function(files) {
    console.log(files);
}).catch(function(e) {
    console.error(e); {
});
Phil Mander
fonte
1

Por diversão, aqui está uma versão baseada em fluxo que funciona com a biblioteca de fluxos highland.js. Foi co-autoria de Victor Vu.

###
  directory >---m------> dirFilesStream >---------o----> out
                |                                 |
                |                                 |
                +--------< returnPipe <-----------+

  legend: (m)erge  (o)bserve

 + directory         has the initial file
 + dirListStream     does a directory listing
 + out               prints out the full path of the file
 + returnPipe        runs stat and filters on directories

###

_ = require('highland')
fs = require('fs')
fsPath = require('path')

directory = _(['someDirectory'])
mergePoint = _()
dirFilesStream = mergePoint.merge().flatMap((parentPath) ->
  _.wrapCallback(fs.readdir)(parentPath).sequence().map (path) ->
    fsPath.join parentPath, path
)
out = dirFilesStream
# Create the return pipe
returnPipe = dirFilesStream.observe().flatFilter((path) ->
  _.wrapCallback(fs.stat)(path).map (v) ->
    v.isDirectory()
)
# Connect up the merge point now that we have all of our streams.
mergePoint.write directory
mergePoint.write returnPipe
mergePoint.end()
# Release backpressure.  This will print files as they are discovered
out.each H.log
# Another way would be to queue them all up and then print them all out at once.
# out.toArray((files)-> console.log(files))
Michael Connor
fonte
1

Usando promessas ( Q ) para resolver isso em um estilo funcional:

var fs = require('fs'),
    fsPath = require('path'),
    Q = require('q');

var walk = function (dir) {
  return Q.ninvoke(fs, 'readdir', dir).then(function (files) {

    return Q.all(files.map(function (file) {

      file = fsPath.join(dir, file);
      return Q.ninvoke(fs, 'lstat', file).then(function (stat) {

        if (stat.isDirectory()) {
          return walk(file);
        } else {
          return [file];
        }
      });
    }));
  }).then(function (files) {
    return files.reduce(function (pre, cur) {
      return pre.concat(cur);
    });
  });
};

Ele retorna a promessa de uma matriz, para que você possa usá-lo como:

walk('/home/mypath').then(function (files) { console.log(files); });
Gunar Gessner
fonte
1

Devo adicionar a biblioteca de lixadeira baseada em promessa à lista.

 var sander = require('sander');
 sander.lsr(directory).then( filenames => { console.log(filenames) } );
IvanSanchez
fonte
1

Usando a promessa bluebird.coroutine:

let promise = require('bluebird'),
    PC = promise.coroutine,
    fs = promise.promisifyAll(require('fs'));
let getFiles = PC(function*(dir){
    let files = [];
    let contents = yield fs.readdirAsync(dir);
    for (let i = 0, l = contents.length; i < l; i ++) {
        //to remove dot(hidden) files on MAC
        if (/^\..*/.test(contents[i])) contents.splice(i, 1);
    }
    for (let i = 0, l = contents.length; i < l; i ++) {
        let content = path.resolve(dir, contents[i]);
        let contentStat = yield fs.statAsync(content);
        if (contentStat && contentStat.isDirectory()) {
            let subFiles = yield getFiles(content);
            files = files.concat(subFiles);
        } else {
            files.push(content);
        }
    }
    return files;
});
//how to use
//easy error handling in one place
getFiles(your_dir).then(console.log).catch(err => console.log(err));
alexcres
fonte
0

Porque todo mundo deveria escrever o seu, eu fiz um.

walk (dir, cb, endCb) cb (arquivo) endCb (err | nulo)

SUJO

module.exports = walk;

function walk(dir, cb, endCb) {
  var fs = require('fs');
  var path = require('path');

  fs.readdir(dir, function(err, files) {
    if (err) {
      return endCb(err);
    }

    var pending = files.length;
    if (pending === 0) {
      endCb(null);
    }
    files.forEach(function(file) {
      fs.stat(path.join(dir, file), function(err, stats) {
        if (err) {
          return endCb(err)
        }

        if (stats.isDirectory()) {
          walk(path.join(dir, file), cb, function() {
            pending--;
            if (pending === 0) {
              endCb(null);
            }
          });
        } else {
          cb(path.join(dir, file));
          pending--;
          if (pending === 0) {
            endCb(null);
          }
        }
      })
    });

  });
}
vvo
fonte
0

confira loaddir https://npmjs.org/package/loaddir

npm install loaddir

  loaddir = require('loaddir')

  allJavascripts = []
  loaddir({
    path: __dirname + '/public/javascripts',
    callback: function(){  allJavascripts.push(this.relativePath + this.baseName); }
  })

Você pode usar em fileNamevez de baseNamese precisar da extensão também.

Um bônus adicional é que ele também assiste os arquivos e chama o retorno de chamada novamente. Existem inúmeras opções de configuração para torná-lo extremamente flexível.

Acabei guardde refazer a gema do ruby ​​usando o loaddir em pouco tempo

Funkodebat
fonte
0

Esta é a minha resposta. Espero que possa ajudar alguém.

Meu foco é fazer com que a rotina de pesquisa possa parar em qualquer lugar e, para um arquivo encontrado, informe a profundidade relativa do caminho original.

var _fs = require('fs');
var _path = require('path');
var _defer = process.nextTick;

// next() will pop the first element from an array and return it, together with
// the recursive depth and the container array of the element. i.e. If the first
// element is an array, it'll be dug into recursively. But if the first element is
// an empty array, it'll be simply popped and ignored.
// e.g. If the original array is [1,[2],3], next() will return [1,0,[[2],3]], and
// the array becomes [[2],3]. If the array is [[[],[1,2],3],4], next() will return
// [1,2,[2]], and the array becomes [[[2],3],4].
// There is an infinity loop `while(true) {...}`, because I optimized the code to
// make it a non-recursive version.
var next = function(c) {
    var a = c;
    var n = 0;
    while (true) {
        if (a.length == 0) return null;
        var x = a[0];
        if (x.constructor == Array) {
            if (x.length > 0) {
                a = x;
                ++n;
            } else {
                a.shift();
                a = c;
                n = 0;
            }
        } else {
            a.shift();
            return [x, n, a];
        }
    }
}

// cb is the callback function, it have four arguments:
//    1) an error object if any exception happens;
//    2) a path name, may be a directory or a file;
//    3) a flag, `true` means directory, and `false` means file;
//    4) a zero-based number indicates the depth relative to the original path.
// cb should return a state value to tell whether the searching routine should
// continue: `true` means it should continue; `false` means it should stop here;
// but for a directory, there is a third state `null`, means it should do not
// dig into the directory and continue searching the next file.
var ls = function(path, cb) {
    // use `_path.resolve()` to correctly handle '.' and '..'.
    var c = [ _path.resolve(path) ];
    var f = function() {
        var p = next(c);
        p && s(p);
    };
    var s = function(p) {
        _fs.stat(p[0], function(err, ss) {
            if (err) {
                // use `_defer()` to turn a recursive call into a non-recursive call.
                cb(err, p[0], null, p[1]) && _defer(f);
            } else if (ss.isDirectory()) {
                var y = cb(null, p[0], true, p[1]);
                if (y) r(p);
                else if (y == null) _defer(f);
            } else {
                cb(null, p[0], false, p[1]) && _defer(f);
            }
        });
    };
    var r = function(p) {
        _fs.readdir(p[0], function(err, files) {
            if (err) {
                cb(err, p[0], true, p[1]) && _defer(f);
            } else {
                // not use `Array.prototype.map()` because we can make each change on site.
                for (var i = 0; i < files.length; i++) {
                    files[i] = _path.join(p[0], files[i]);
                }
                p[2].unshift(files);
                _defer(f);
            }
        });
    }
    _defer(f);
};

var printfile = function(err, file, isdir, n) {
    if (err) {
        console.log('-->   ' + ('[' + n + '] ') + file + ': ' + err);
        return true;
    } else {
        console.log('... ' + ('[' + n + '] ') + (isdir ? 'D' : 'F') + ' ' + file);
        return true;
    }
};

var path = process.argv[2];
ls(path, printfile);
manbaum
fonte
0

Aqui está um método recursivo de obter todos os arquivos, incluindo subdiretórios.

const FileSystem = require("fs");
const Path = require("path");

//...

function getFiles(directory) {
    directory = Path.normalize(directory);
    let files = FileSystem.readdirSync(directory).map((file) => directory + Path.sep + file);

    files.forEach((file, index) => {
        if (FileSystem.statSync(file).isDirectory()) {
            Array.prototype.splice.apply(files, [index, 1].concat(getFiles(file)));
        }
    });

    return files;
}
Daniel
fonte
0

Outro simples e útil

function walkDir(root) {
    const stat = fs.statSync(root);

    if (stat.isDirectory()) {
        const dirs = fs.readdirSync(root).filter(item => !item.startsWith('.'));
        let results = dirs.map(sub => walkDir(`${root}/${sub}`));
        return [].concat(...results);
    } else {
        return root;
    }
}
clinyong
fonte
Você está assumindo que todo arquivo no diretório raiz é uma pasta aqui.
Xechelonx
0

É assim que uso a função nodejs fs.readdir para pesquisar recursivamente um diretório.

const fs = require('fs');
const mime = require('mime-types');
const readdirRecursivePromise = path => {
    return new Promise((resolve, reject) => {
        fs.readdir(path, (err, directoriesPaths) => {
            if (err) {
                reject(err);
            } else {
                if (directoriesPaths.indexOf('.DS_Store') != -1) {
                    directoriesPaths.splice(directoriesPaths.indexOf('.DS_Store'), 1);
                }
                directoriesPaths.forEach((e, i) => {
                    directoriesPaths[i] = statPromise(`${path}/${e}`);
                });
                Promise.all(directoriesPaths).then(out => {
                    resolve(out);
                }).catch(err => {
                    reject(err);
                });
            }
        });
    });
};
const statPromise = path => {
    return new Promise((resolve, reject) => {
        fs.stat(path, (err, stats) => {
            if (err) {
                reject(err);
            } else {
                if (stats.isDirectory()) {
                    readdirRecursivePromise(path).then(out => {
                        resolve(out);
                    }).catch(err => {
                        reject(err);
                    });
                } else if (stats.isFile()) {
                    resolve({
                        'path': path,
                        'type': mime.lookup(path)
                    });
                } else {
                    reject(`Error parsing path: ${path}`);
                }
            }
        });
    });
};
const flatten = (arr, result = []) => {
    for (let i = 0, length = arr.length; i < length; i++) {
        const value = arr[i];
        if (Array.isArray(value)) {
            flatten(value, result);
        } else {
            result.push(value);
        }
    }
    return result;
};

Digamos que você tenha um caminho chamado '/ database' na raiz de seus projetos de nós. Depois que essa promessa é resolvida, ela deve cuspir uma matriz de todos os arquivos em '/ database'.

readdirRecursivePromise('database').then(out => {
    console.log(flatten(out));
}).catch(err => {
    console.log(err);
});
Jason Clay
fonte