localizar arquivos por extensão, * .html em uma pasta em nodejs

89

Eu gostaria de encontrar todos os arquivos * .html na pasta src e todas as suas subpastas usando nodejs. Qual a melhor maneira de fazer isso?

var folder = '/project1/src';
var extension = 'html';
var cb = function(err, results) {
   // results is an array of the files with path relative to the folder
   console.log(results);

}
// This function is what I am looking for. It has to recursively traverse all sub folders. 
findFiles(folder, extension, cb);

Acho que muitos desenvolvedores deveriam ter uma solução excelente e testada e é melhor usá-la do que escrever uma sozinho.

Nicolas S.Xu
fonte
Se você deseja pesquisar arquivos por regex, use a biblioteca file-regex , que faz pesquisa recursiva de arquivos simultaneamente.
Akash Babu

Respostas:

90

node.js, função simples recursiva:

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

function fromDir(startPath,filter){

    //console.log('Starting from dir '+startPath+'/');

    if (!fs.existsSync(startPath)){
        console.log("no dir ",startPath);
        return;
    }

    var files=fs.readdirSync(startPath);
    for(var i=0;i<files.length;i++){
        var filename=path.join(startPath,files[i]);
        var stat = fs.lstatSync(filename);
        if (stat.isDirectory()){
            fromDir(filename,filter); //recurse
        }
        else if (filename.indexOf(filter)>=0) {
            console.log('-- found: ',filename);
        };
    };
};

fromDir('../LiteScript','.html');

adicione RegExp se quiser ficar mais sofisticado e um retorno de chamada para torná-lo genérico.

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

function fromDir(startPath,filter,callback){

    //console.log('Starting from dir '+startPath+'/');

    if (!fs.existsSync(startPath)){
        console.log("no dir ",startPath);
        return;
    }

    var files=fs.readdirSync(startPath);
    for(var i=0;i<files.length;i++){
        var filename=path.join(startPath,files[i]);
        var stat = fs.lstatSync(filename);
        if (stat.isDirectory()){
            fromDir(filename,filter,callback); //recurse
        }
        else if (filter.test(filename)) callback(filename);
    };
};

fromDir('../LiteScript',/\.html$/,function(filename){
    console.log('-- found: ',filename);
});
Lucio M. Tato
fonte
muito obrigado pelo código de demonstração! Eu adicionei algo no topo do seu código e funcionou muito bem! Eu também verifiquei seu projeto LiteScript, e é incrível. Eu o marquei no github!
Nicolas S.Xu
Um pequeno script para encontrar nomes de arquivos sem extensão também - no meu caso, eu tinha alguns JPEGs e precisava descobrir se o arquivo original em um diretório diferente era png ou jpeg, isso ajuda
Ricky Odin Matthews
78

eu gosto de usar o pacote glob :

const glob = require('glob');

glob(__dirname + '/**/*.html', {}, (err, files)=>{
  console.log(files)
})
David Cheung
fonte
1
Normalmente não sou um fã de pacotes para coisas simples, mas é apenas uma questão de tempo antes que o glob tenha uma implementação de node js embutida. Isso está se tornando a expressão regular da seleção de arquivos.
Seph Reed
27

O quê, espera aí ?! ... Ok, talvez isso faça mais sentido para outras pessoas também.

[ nodejs 7 veja bem]

fs = import('fs');
let dirCont = fs.readdirSync( dir );
let files = dirCont.filter( function( elm ) {return elm.match(/.*\.(htm?html)/ig);});

Faça o que quer que seja com regex, torne-o um argumento que você definiu na função com um padrão etc.

Mestre James
fonte
2
Isso só obterá arquivos correspondentes no diretório raiz.
dreamerkumar
6
Tentei editar e fui rejeitado, do qual discordo. Aqui está a minha proposta: stackoverflow.com/review/suggested-edits/19188733 wl faz todo o sentido. Além disso, a importação para fs está faltando. As três linhas de que você precisa são: 1. const fs = require('fs');2. const dirCont = fs.readdirSync( dir );3.const files = dirCont.filter( ( elm ) => /.*\.(htm?html)/gi.test(elm) );
Avindra Goolcharan,
certo desculpe wl.fs é onde eu armazenei o lib do fs via importação.
Mestre James
oh importar é provavelmente minha própria função customizada que aponta para a necessidade por agora também, então use require ou o que quer que você tenha que fazer.
Mestre James
13

Com base no código do Lucio, fiz um módulo. Ele retornará uma distância com todos os arquivos com extensões específicas em um. Basta postar aqui caso alguém precise.

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


/**
 * Find all files recursively in specific folder with specific extension, e.g:
 * findFilesInDir('./project/src', '.html') ==> ['./project/src/a.html','./project/src/build/index.html']
 * @param  {String} startPath    Path relative to this file or other file which requires this files
 * @param  {String} filter       Extension name, e.g: '.html'
 * @return {Array}               Result files with path string in an array
 */
function findFilesInDir(startPath,filter){

    var results = [];

    if (!fs.existsSync(startPath)){
        console.log("no dir ",startPath);
        return;
    }

    var files=fs.readdirSync(startPath);
    for(var i=0;i<files.length;i++){
        var filename=path.join(startPath,files[i]);
        var stat = fs.lstatSync(filename);
        if (stat.isDirectory()){
            results = results.concat(findFilesInDir(filename,filter)); //recurse
        }
        else if (filename.indexOf(filter)>=0) {
            console.log('-- found: ',filename);
            results.push(filename);
        }
    }
    return results;
}

module.exports = findFilesInDir;
Nicolas S.Xu
fonte
12

Você pode usar o Filehound para fazer isso.

Por exemplo: encontre todos os arquivos .html em / tmp:

const Filehound = require('filehound');

Filehound.create()
  .ext('html')
  .paths("/tmp")
  .find((err, htmlFiles) => {
    if (err) return console.error("handle err", err);

    console.log(htmlFiles);
});

Para mais informações (e exemplos), verifique os documentos: https://github.com/nspragg/filehound

Isenção de responsabilidade : eu sou o autor.

nickool
fonte
8

Eu olhei as respostas acima e misturei esta versão que funciona para mim:

function getFilesFromPath(path, extension) {
    let files = fs.readdirSync( path );
    return files.filter( file => file.match(new RegExp(`.*\.(${extension})`, 'ig')));
}

console.log(getFilesFromPath("./testdata", ".txt"));

Este teste retornará uma matriz de nomes de arquivos dos arquivos encontrados na pasta no caminho ./testdata. Trabalhando no nó versão 8.11.3.

Netsi1964
fonte
1
Eu adicionaria $ no final da RegExp:.*\.(${extension})$
Eugene
3

Você pode usar a ajuda do sistema operacional para isso. Aqui está uma solução de plataforma cruzada:

1. A função abaixo usa lse dire não pesquisa recursivamente, mas tem caminhos relativos

var exec = require('child_process').exec;
function findFiles(folder,extension,cb){
    var command = "";
    if(/^win/.test(process.platform)){
        command = "dir /B "+folder+"\\*."+extension;
    }else{
        command = "ls -1 "+folder+"/*."+extension;
    }
    exec(command,function(err,stdout,stderr){
        if(err)
            return cb(err,null);
        //get rid of \r from windows
        stdout = stdout.replace(/\r/g,"");
        var files = stdout.split("\n");
        //remove last entry because it is empty
        files.splice(-1,1);
        cb(err,files);
    });
}

findFiles("folderName","html",function(err,files){
    console.log("files:",files);
})

2. A função abaixo usa finde dir, pesquisa recursivamente, mas no windows tem caminhos absolutos

var exec = require('child_process').exec;
function findFiles(folder,extension,cb){
    var command = "";
    if(/^win/.test(process.platform)){
        command = "dir /B /s "+folder+"\\*."+extension;
    }else{
        command = 'find '+folder+' -name "*.'+extension+'"'
    }
    exec(command,function(err,stdout,stderr){
        if(err)
            return cb(err,null);
        //get rid of \r from windows
        stdout = stdout.replace(/\r/g,"");
        var files = stdout.split("\n");
        //remove last entry because it is empty
        files.splice(-1,1);
        cb(err,files);
    });
}

findFiles("folder","html",function(err,files){
    console.log("files:",files);
})
Emil Condrea
fonte
1
Nunca pensei que pudesse ser feito dessa forma, já que não estou familiarizado com o require ('child_process'). Exec, mas parece muito bom e inspira muitos pensamentos em mim. Obrigado!
Nicolas S.Xu
2
Esta não é a maneira de fazer "usando nodejs". Isso é usar o sistema operacional, iniciar outro processo, etc. Também falha se houver um diretório terminando em ".html", por exemplo: files.html /
Lucio M. Tato
@ LucioM.Tato você pode especificar o tipo de arquivo ao pesquisar. Existem muitas soluções para um problema; se uma delas não corresponder à sua ideia, não significa que esteja errada, é apenas diferente. Essa resposta prova que você pode reutilizar as soluções existentes, independentemente da linguagem de script usada.
Emil Condrea
Claro que não há nada de errado em iterar em um diretório e encontrar os arquivos com determinada extensão, mas eu só queria receber do SO todas essas informações porque sabia que ele pode fazer isso. :)
Emil Condrea
@EmilCondrea, IHMO isto não está "usando o nó" como o OP pediu. De qualquer forma, removerei o downvote se isso estiver incomodando você.
Lucio M. Tato
3

O código a seguir faz uma pesquisa recursiva dentro de ./ (altere-o apropriadamente) e retorna uma matriz de nomes de arquivos absolutos terminando com .html

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

var searchRecursive = function(dir, pattern) {
  // This is where we store pattern matches of all files inside the directory
  var results = [];

  // Read contents of directory
  fs.readdirSync(dir).forEach(function (dirInner) {
    // Obtain absolute path
    dirInner = path.resolve(dir, dirInner);

    // Get stats to determine if path is a directory or a file
    var stat = fs.statSync(dirInner);

    // If path is a directory, scan it and combine results
    if (stat.isDirectory()) {
      results = results.concat(searchRecursive(dirInner, pattern));
    }

    // If path is a file and ends with pattern then push it onto results
    if (stat.isFile() && dirInner.endsWith(pattern)) {
      results.push(dirInner);
    }
  });

  return results;
};

var files = searchRecursive('./', '.html'); // replace dir and pattern
                                                // as you seem fit

console.log(files);
Nikhil
fonte
2

Não é possível adicionar um comentário devido à reputação, mas observe o seguinte:

Usar fs.readdir ou node-glob para localizar um conjunto curinga de arquivos em uma pasta de 500.000 arquivos levou cerca de 2s. Usar exec com DIR demorou ~ 0,05s (não recursivo) ou ~ 0,45s (recursivo). (Eu estava procurando por ~ 14 arquivos correspondentes ao meu padrão em um único diretório).

Até agora, não consegui encontrar nenhuma implementação de nodejs que use curinga de SO de baixo nível em busca de eficiência. Mas o código baseado em DIR / ls acima funciona maravilhosamente no Windows em termos de eficiência. O linux find, entretanto, provavelmente será muito lento para diretórios grandes.

Simon H
fonte
Interessante, de fato.
philk
Observe que há novas funções no módulo nodejs fs mais recente (12.13+? Diretório iterado fns?). Ainda não os experimentei porque estou preso em 6.9.11 por enquanto; será interessante ver se eles fornecem novos recursos úteis para isso. Pensando na minha postagem agora; O armazenamento em cache do SO também deve ser considerado. Meus 0,05s provavelmente teriam sido medidos DEPOIS de tê-lo executado várias vezes. Eu me pergunto qual é a velocidade do PRIMEIRO 'DIR'?
Simon H
1

meus dois pence, usando map em vez de for-loop

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

var findFiles = function(folder, pattern = /.*/, callback) {
  var flist = [];

  fs.readdirSync(folder).map(function(e){ 
    var fname = path.join(folder, e);
    var fstat = fs.lstatSync(fname);
    if (fstat.isDirectory()) {
      // don't want to produce a new array with concat
      Array.prototype.push.apply(flist, findFiles(fname, pattern, callback)); 
    } else {
      if (pattern.test(fname)) {
        flist.push(fname);
        if (callback) {
          callback(fname);
        }
      }
    }
  });
  return flist;
};

// HTML files   
var html_files = findFiles(myPath, /\.html$/, function(o) { console.log('look what we have found : ' + o} );

// All files
var all_files = findFiles(myPath);
jset74
fonte
1

Dê uma olhada em file-regex

let findFiles = require('file-regex')
let pattern = '\.js'

findFiles(__dirname, pattern, (err, files) => {  
   console.log(files);
})

Este trecho acima imprimirá todos os jsarquivos no diretório atual.

Akash Babu
fonte
Essa é a solução mais fácil que existe.
kyeno
0

Eu só percebi, você está usando métodos fs de sincronização, que pode bloquear a sua aplicação, aqui é um assíncrono maneira usando baseado promessa- assíncrono e q , você pode executá-lo com start = / MyFolder myfile.js FILTER = "jpg". Nó, presumindo que você coloque o seguinte código em um arquivo chamado myfile.js:

Q = require("q")
async = require("async")
path = require("path")
fs = require("fs")

function findFiles(startPath, filter, files){
    var deferred;
    deferred = Q.defer(); //main deferred

    //read directory
    Q.nfcall(fs.readdir, startPath).then(function(list) {
        var ideferred = Q.defer(); //inner deferred for resolve of async each
        //async crawling through dir
        async.each(list, function(item, done) {

            //stat current item in dirlist
            return Q.nfcall(fs.stat, path.join(startPath, item))
                .then(function(stat) {
                    //check if item is a directory
                    if (stat.isDirectory()) {
                        //recursive!! find files in subdirectory
                        return findFiles(path.join(startPath, item), filter, files)
                            .catch(function(error){
                                console.log("could not read path: " + error.toString());
                            })
                            .finally(function() {
                                //resolve async job after promise of subprocess of finding files has been resolved
                                return done();
                             });
                    //check if item is a file, that matches the filter and add it to files array
                    } else if (item.indexOf(filter) >= 0) {
                        files.push(path.join(startPath, item));
                        return done();
                    //file is no directory and does not match the filefilter -> don't do anything
                    } else {
                        return done();
                    }
                })
                .catch(function(error){
                    ideferred.reject("Could not stat: " + error.toString());
                });
        }, function() {
            return ideferred.resolve(); //async each has finished, so resolve inner deferred
        });
        return ideferred.promise;
    }).then(function() {
        //here you could do anything with the files of this recursion step (otherwise you would only need ONE deferred)
        return deferred.resolve(files); //resolve main deferred
    }).catch(function(error) {
        deferred.reject("Could not read dir: " + error.toString());
        return
    });
    return deferred.promise;
}


findFiles(process.env.START, process.env.FILTER, [])
    .then(function(files){
        console.log(files);
    })
    .catch(function(error){
        console.log("Problem finding files: " + error);
})
Christoph Johannsdotter
fonte
4
Um ótimo exemplo de inferno de callback! :)
Afshin Moazami
2
tem razão, não faria assim de novo: D Talvez encontre tempo nos próximos dias, resolvendo com async / await para mostrar a diferença.
Christoph Johannsdotter
0

Instalar

você pode instalar este pacote walk-sync por

yarn add walk-sync

Uso

const walkSync = require("walk-sync");
const paths = walkSync("./project1/src", {globs: ["**/*.html"]});
console.log(paths);   //all html file path array
Muhammad Numan
fonte
-2

Postagem antiga, mas o ES6 agora trata disso fora da caixa com o includesmétodo.

let files = ['file.json', 'other.js'];

let jsonFiles = files.filter(file => file.includes('.json'));

console.log("Files: ", jsonFiles) ==> //file.json
James
fonte
Vou votar a favor porque eu estava usando file.readdirSynce precisava de uma maneira simples de filtrar arquivos por extensão. Acho que isso responde parte da pergunta neste tópico, mas talvez não tudo. Ainda vale a pena considerar.
justinpage