node.js fs.readdir pesquisa de diretórios recursiva

Alguma idéia em uma pesquisa de diretório assíncrona usando fs.readdir? Eu percebo que poderíamos introduzir recursion e chamar a function de diretório de leitura com o próximo diretório para ler, mas estou um pouco preocupado com isso não sendo asynchronous …

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

Procurando por saída como …

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

    Existem basicamente duas maneiras de realizar isso. Em um ambiente asynchronous, 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 toda iteração do loop seja concluída 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, neste caso, provavelmente é melhor usar um loop paralelo, pois não importa em que ordem o walk termine, contanto que ele seja concluído e retorne os resultados (a menos que você os queira em ordem).

    Um loop paralelo seria 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 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 = 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 (ATENÇÃO: 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 melhorados.

    A. Dê uma olhada no módulo de arquivo . Tem uma function chamada walk:

    file.walk (iniciar, callback)

    Navega em uma tree de arquivos, chamando callback para cada diretório, passando (nulo, dirPath, dirs, arquivos).

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

    B. Uma alternativa, e até um dos meus favoritos: use o unix para find isso. Por que fazer algo de novo, que já foi programado? Talvez não 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 mecanismo de cache embutido que torna as pesquisas subseqüentes muito rápidas, desde que apenas poucas pastas tenham sido alteradas.

    Apenas no caso de alguém achar útil, eu também reúno 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 resources ao filtrar. Filtrar dentro desta function. Por exemplo, Substitua results.push(file); com 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); 

    Outro bom pacote npm é o glob .

    npm install glob

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

    Editar:

    Eu realmente não estava perfeitamente feliz com glob, então criei readdirp .

    Estou muito confiante de que sua API faz com que encontrar arquivos e diretórios de forma recursiva e aplicar filtros específicos seja muito fácil.

    Leia a documentação para ter uma ideia melhor do que faz e instale via:

    npm install readdirp

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

     var glob = require( 'glob' ); glob( 'dirname/**/*.js', function( err, files ) { console.log( files ); }); 

    Este usa a quantidade máxima de novos resources disponíveis no nó 8, incluindo Promises, util / promisify, desestruturação, async-await, map + reduce e more, fazendo com que seus colegas de trabalho coçam suas cabeças enquanto tentam descobrir o que está acontecendo.

    Nenhuma dependência externa (nó 8+).

     const { promisify } = require('util'); const { resolve } = require('path'); const fs = require('fs'); const readdir = promisify(fs.readdir); const rename = promisify(fs.rename); 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)); 

    Se você quiser usar um pacote npm, a chave é 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 tenha lido nos últimos tempos: o autor desaprovou este pacote em 2015:

    O wrench.js está obsoleto e não foi atualizado há algum tempo. Eu recomendo fortemente o uso do fs-extra para fazer qualquer operação extra no sistema de arquivos.

    Eu amei a resposta do chjj acima e não teria sido capaz de criar minha versão do loop paralelo sem esse começo.

     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. Eu ainda estou começando no reino do NodeJS, então é uma maneira que espero aprender mais.

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

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

    Com recursion

     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 

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

    Confira a biblioteca final-fs . Ele fornece uma function readdirRecursive :

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

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

    Por exemplo, pesquise no diretório de trabalho atual por 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 mais casos de uso e exemplos de uso: https://github.com/nspragg/filehound

    Disclaimer: Eu sou o autor.

    Usando async / wait, 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); } }) }); }; }; 

    Implementação de promise autônoma

    Eu estou usando a biblioteca de promises when.js 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); }); 

    Eu incluí um parâmetro opcional includeDir que includeá diretórios na listview de arquivos se definido como true .

    klaw e klaw-sync valem a pena considerar para esse tipo de coisa. Estes eram parte do nó-fs-extra .

    Aqui está outra implementação. Nenhuma das soluções acima tem limitadores e, portanto, se a sua estrutura de diretórios for grande, todos eles vão se debater e, eventualmente, ficar sem resources.

     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 implementações mais simples para estruturas de diretórios pequenos.

    O módulo recursive-readdir possui essa funcionalidade.

    Eu modifiquei a resposta baseada em Promise de Trevor Senior para trabalhar com 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); { }); 

    Por diversão, aqui está uma versão baseada em stream que funciona com a biblioteca de streams 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)) 

    Usando promises ( 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 uma promise de um array, então você pode usá-lo como:

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

    Eu devo adicionar a biblioteca de lixeira baseada em Promise à lista.

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

    Usando o bluebird promise.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)); 

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

    andar (dir, cb, endCb) cb (arquivo) endCb (err | null)

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

    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 fileName vez de baseName se precisar da extensão também.

    Um bônus adicional é que ele irá assistir os arquivos também e chamar o callback novamente. Existem toneladas de opções de configuração para torná-lo extremamente flexível.

    Acabei de refazer a gema de guard do ruby ​​usando loaddir em pouco tempo

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

    Meu foco é fazer com que a rotina de busca possa parar em qualquer lugar e, para um arquivo encontrado, informe a profundidade relativa ao 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. ie 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. // eg 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); 

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

    Estou relutante em adicionar outra resposta à pilha, mas gostei dessa resposta (a única que usou async / await), mas achei que precisava de um pouco de limpeza e simplificação:

     async function getFileDescendents(dir) { const fs = require('fs-promise'), path = require('path'); let files = await fs.readdir(dir); let result = files.map(file => { let p = path.join(dir,file); return fs.stat(p).then(stat => stat.isDirectory() ? getFileDescendents(p) : p); }); return Array.prototype.concat(...(await Promise.all(result))); // flatten } 

    É usado assim:

     let files = await getFileDescendents('./my_directory'); 

    É assim que eu uso a function 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 do seu projeto de nó. Uma vez que esta promise seja resolvida, deve-se citar um array de cada arquivo em '/ database'.

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

    Ainda outra resposta, mas desta vez usando o TypeScript:

     /** * Recursively walk a directory asynchronously and obtain all file names (with full path). * * @param dir Folder name you want to recursively process * @param done Callback function, returns all files with full path. * @param filter Optional filter to specify which files to include, * eg for json files: (f: string) => /.json$/.test(f) */ const walk = ( dir: string, done: (err: Error | null, results ? : string[]) => void, filter ? : (f: string) => boolean ) => { let results: string[] = []; fs.readdir(dir, (err: Error, list: string[]) => { if (err) { return done(err); } let pending = list.length; if (!pending) { return done(null, results); } list.forEach((file: string) => { file = path.resolve(dir, file); fs.stat(file, (err2, stat) => { if (stat && stat.isDirectory()) { walk(file, (err3, res) => { if (res) { results = results.concat(res); } if (!--pending) { done(null, results); } }, filter); } else { if (typeof filter === 'undefined' || (filter && filter(file))) { results.push(file); } if (!--pending) { done(null, results); } } }); }); }); };