Ler um arquivo uma linha de cada vez no node.js?

553

Estou tentando ler um arquivo grande, uma linha por vez. Encontrei uma pergunta no Quora que tratava do assunto, mas estou perdendo algumas conexões para fazer a coisa toda se encaixar.

 var Lazy=require("lazy");
 new Lazy(process.stdin)
     .lines
     .forEach(
          function(line) { 
              console.log(line.toString()); 
          }
 );
 process.stdin.resume();

O pouco que eu gostaria de descobrir é como eu poderia ler uma linha de cada vez de um arquivo em vez de STDIN, como neste exemplo.

Eu tentei:

 fs.open('./VeryBigFile.csv', 'r', '0666', Process);

 function Process(err, fd) {
    if (err) throw err;
    // DO lazy read 
 }

mas não está funcionando. Eu sei que em uma pitada eu poderia voltar a usar algo como PHP, mas eu gostaria de descobrir isso.

Eu não acho que a outra resposta funcionaria, pois o arquivo é muito maior do que o servidor no qual estou executando ele tem memória.

Alex C
fonte
2
Isso acaba sendo bastante difícil usando apenas um nível baixo fs.readSync(). Você pode ler octetos binários em um buffer, mas não há maneira fácil de lidar com caracteres UTF-8 ou UTF-16 parciais sem inspecionar o buffer antes de convertê-lo em strings JavaScript e procurar EOLs. O Buffer()tipo não possui um conjunto de funções tão rico para operar em suas instâncias quanto as cadeias nativas, mas as cadeias nativas não podem conter dados binários. Parece-me que a falta de uma maneira integrada de ler linhas de texto de manipuladores de arquivos arbitrários é uma lacuna real no node.js.
Hippietrail
5
As linhas vazias lidas por esse método são convertidas em uma linha com um único 0 (código de caractere real para 0) nelas. Eu tive que cortar essa linha lá:if (line.length==1 && line[0] == 48) special(line);
Thabo
2
Pode-se também usar o pacote 'linha por linha', que faz o trabalho perfeitamente.
Patrice
1
Por favor, atualize a questão de dizer que a solução é usar um fluxo de transformar
Gabriel Llamas
2
@DanDascalescu Se quiser, você pode adicionar este à lista: o seu exemplo desembarcou ligeiramente modificada em node's docs API github.com/nodejs/node/pull/4609
eljefedelrodeodeljefe

Respostas:

790

Desde o Node.js. v0.12 e a partir do Node.js. v4.0.0, existe um módulo principal do readline estável . Aqui está a maneira mais fácil de ler linhas de um arquivo, sem módulos externos:

const fs = require('fs');
const readline = require('readline');

async function processLineByLine() {
  const fileStream = fs.createReadStream('input.txt');

  const rl = readline.createInterface({
    input: fileStream,
    crlfDelay: Infinity
  });
  // Note: we use the crlfDelay option to recognize all instances of CR LF
  // ('\r\n') in input.txt as a single line break.

  for await (const line of rl) {
    // Each line in input.txt will be successively available here as `line`.
    console.log(`Line from file: ${line}`);
  }
}

processLineByLine();

Ou alternativamente:

var lineReader = require('readline').createInterface({
  input: require('fs').createReadStream('file.in')
});

lineReader.on('line', function (line) {
  console.log('Line from file:', line);
});

A última linha é lida corretamente (a partir do Nó v0.12 ou posterior), mesmo se não houver final \n.

UPDATE : este exemplo foi adicionado à documentação oficial da API do Node .

Dan Dascalescu
fonte
7
você precisa de um terminal: false na definição createInterface
glasspill
64
Como determinar a última linha? Ao capturar um evento "close":rl.on('close', cb)
Green
27
O Readline é para uma finalidade semelhante ao GNU Readline , não para ler arquivos linha por linha. Existem várias advertências ao usá-lo para ler arquivos e essa não é uma prática recomendada.
Nakedible 10/10/2015
8
@ Nakedible: interessante. Você poderia postar uma resposta com um método melhor?
Dan Dascalescu 14/10
6
Considero github.com/jahewson/node-byline a melhor implementação da leitura linha por linha, mas as opiniões podem variar.
Nakedible
165

Para uma operação tão simples, não deve haver dependência de módulos de terceiros. Vá com calma.

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

var rd = readline.createInterface({
    input: fs.createReadStream('/path/to/file'),
    output: process.stdout,
    console: false
});

rd.on('line', function(line) {
    console.log(line);
});
kofrasa
fonte
33
infelizmente, essa solução atraente não funciona corretamente - os lineeventos acontecem somente após o acerto \n, ou seja, todas as alternativas são perdidas (consulte unicode.org/reports/tr18/#Line_Boundaries ). # 2, os dados após o último \nsão ignorados silenciosamente (consulte stackoverflow.com/questions/18450197/… ). Eu chamaria essa solução de perigosa porque ela funciona em 99% de todos os arquivos e em 99% dos dados, mas falha silenciosamente nos demais. sempre que fs.writeFileSync( path, lines.join('\n'))você escrever um arquivo que será apenas parcialmente lido pela solução acima.
flow
4
Há um problema com esta solução. Se você usa your.js <lines.txt, não obtém a última linha. Se não tiver um '\ n' no final do curso.
precisa saber é o seguinte
O readlinepacote se comporta de maneiras verdadeiramente bizarras para um programador Unix / Linux experiente.
Pointy
11
rd.on("close", ..);podem ser utilizados como uma chamada de retorno (occurrs quando todas as linhas são lidas)
Luca Steeb
6
O problema "dados após o último \ n" parece ter sido resolvido na minha versão do nó (0.12.7). Por isso, prefiro essa resposta, que parece a mais simples e a mais elegante.
Myk Melez 01/09/2015
63

Você não precisa opendo arquivo, mas sim criar um arquivo ReadStream.

fs.createReadStream

Depois passe esse fluxo para Lazy

Raynos
fonte
2
Existe algo como um evento final para o Lazy? Quando todas as linhas foram lidas?
Max
1
@Max, Tente:new lazy(fs.createReadStream('...')).lines.forEach(function(l) { /* ... */ }).join(function() { /* Done */ })
Cecchi
6
@Cecchi e @Max, não use join porque ele armazenará em buffer todo o arquivo na memória. Em vez disso, basta ouvir o evento 'end':new lazy(...).lines.forEach(...).on('end', function() {...})
Corin
3
@Cecchi, @Corin e @Max: Pelo que vale a pena, eu me enlouqueci .on('end'... depois .forEach(...) , quando na verdade tudo se comportava como esperado quando liguei o evento primeiro .
crowjonah
52
Esse resultado é muito alto nos resultados de pesquisa; portanto, vale a pena notar que o Lazy parece abandonado. Passaram 7 meses sem nenhuma alteração e apresentam alguns bugs terríveis (última linha ignorada, vazamentos maciços de memória, etc.).
blu
38

existe um módulo muito bom para ler um arquivo linha por linha, chamado leitor de linha

com isso você simplesmente escreve:

var lineReader = require('line-reader');

lineReader.eachLine('file.txt', function(line, last) {
  console.log(line);
  // do whatever you want with line...
  if(last){
    // or check if it's the last one
  }
});

você pode até iterar o arquivo com uma interface "estilo java", se precisar de mais controle:

lineReader.open('file.txt', function(reader) {
  if (reader.hasNextLine()) {
    reader.nextLine(function(line) {
      console.log(line);
    });
  }
});
polaretto
fonte
4
Isso funciona bem. Ele até lê a última linha (!). Vale ressaltar que ele mantém o \ r se for um arquivo de texto no estilo Windows. line.trim () faz o truque de remover o extra \ r.
Pierre-Luc Bertrand
É subótimo, pois a entrada só pode ser de um arquivo nomeado e não (para um exemplo óbvio e extremamente importante process/stdin). Pelo menos, se puder, certamente não é óbvio lendo o código e tentando-o.
Pointy
2
Enquanto isso, há uma maneira integrada de ler linhas de um arquivo, usando o readlinemódulo principal .
Dan Dascalescu 16/09
Isso é antigo, mas no caso de alguém tropeçar nele: function(reader)e function(line)deve ser: function(err,reader)e function(err,line).
jallmer
1
Apenas para o registro, line-readerlê o arquivo de forma assíncrona. A alternativa síncrona éline-reader-sync
Prajwal Dhatwalia
31
require('fs').readFileSync('file.txt', 'utf-8').split(/\r?\n/).forEach(function(line){
  console.log(line);
})
John Williams
fonte
42
Isso lerá o arquivo inteiro na memória e depois o dividirá em linhas. Não é o que as perguntas fazem. O objetivo é ser capaz de ler arquivos grandes sequencialmente, sob demanda.
Dan Dascalescu 25/10
2
Isso se encaixa no meu caso de uso, eu estava procurando uma maneira simples de converter a entrada de um script em outro formato. Obrigado!
Callat 12/08/19
23

Atualização em 2019

Um exemplo impressionante já foi publicado na documentação oficial do Nodejs. aqui

Isso requer que o Nodejs mais recente esteja instalado em sua máquina. > 11,4

const fs = require('fs');
const readline = require('readline');

async function processLineByLine() {
  const fileStream = fs.createReadStream('input.txt');

  const rl = readline.createInterface({
    input: fileStream,
    crlfDelay: Infinity
  });
  // Note: we use the crlfDelay option to recognize all instances of CR LF
  // ('\r\n') in input.txt as a single line break.

  for await (const line of rl) {
    // Each line in input.txt will be successively available here as `line`.
    console.log(`Line from file: ${line}`);
  }
}

processLineByLine();
Desenvolvedor-chefe
fonte
esta resposta é muito melhor do que qualquer coisa acima, graças ao seu comportamento baseado em promessas, indicando distintamente o EOF.
precisa saber é o seguinte
Obrigado, que fofo.
Goran Stoyanov
3
Talvez isso seja óbvio para os outros, mas demorei um pouco para depurar: se houver algum awaits entre a createInterface()chamada e o início do for awaitloop, você misteriosamente perderá linhas desde o início do arquivo. createInterface()imediatamente começa a emitir linhas nos bastidores, e o iterador assíncrono criado implicitamente com const line of rlnão pode começar a ouvir essas linhas até que elas sejam criadas.
precisa saber é o seguinte
19

Tópico antigo, mas isso funciona:

var rl = readline.createInterface({
      input : fs.createReadStream('/path/file.txt'),
      output: process.stdout,
      terminal: false
})
rl.on('line',function(line){
     console.log(line) //or parse line
})

Simples. Não há necessidade de um módulo externo.

nf071590
fonte
2
Se você obtiver readline is not definedou fs is not defined, adicione var readline = require('readline');e var fs = require('fs');faça com que isso funcione. Caso contrário, doce, doce código. Obrigado.
bergie3000
12
Esta resposta é uma fraude exata de uma resposta anterior , mas sem os comentários avisando que o pacote readline está marcado como instável (ainda instável em abril de 2015) e, em meados de 2013, teve problemas para ler as últimas linhas de um arquivo sem terminações de linha . O último problema de linha surgiu na primeira vez que o usei na v0.10.35 e depois foi embora. / argh
ruffin
Você não precisa especificar a saída se tudo o que você faz é ler de um fluxo de arquivos .
Dan Dascalescu 16/09
18

Você sempre pode rolar seu próprio leitor de linha. Ainda não comparei esse snippet, mas ele divide corretamente o fluxo de pedaços de entrada em linhas sem o \ \ n 'final'

var last = "";

process.stdin.on('data', function(chunk) {
    var lines, i;

    lines = (last+chunk).split("\n");
    for(i = 0; i < lines.length - 1; i++) {
        console.log("line: " + lines[i]);
    }
    last = lines[i];
});

process.stdin.on('end', function() {
    console.log("line: " + last);
});

process.stdin.resume();

Eu criei isso ao trabalhar em um script rápido de análise de log que precisava acumular dados durante a análise de log e achei que seria bom tentar fazer isso usando js e node em vez de usar perl ou bash.

De qualquer forma, acho que os pequenos scripts do nodejs devem ser independentes e não contar com módulos de terceiros. Portanto, depois de ler todas as respostas para essa pergunta, cada um usando vários módulos para lidar com a análise de linha, uma solução nativa do 13 SLOC nodejs pode ser interessante.

Ernelli
fonte
Não parece haver nenhuma maneira trivial de estender isso para trabalhar com arquivos arbitrários além de apenas stdin... a menos que eu esteja sentindo falta de algo.
Hippietrail
3
@hippietrail você pode criar uma ReadStreamcom fs.createReadStream('./myBigFile.csv')e usá-lo em vez destdin
nolith
2
É garantido que cada pedaço contenha apenas linhas completas? Os caracteres UTF-8 de vários bytes garantem que não sejam divididos nos limites dos blocos?
Hippietrail
1
@hippietrail Eu não acho que caracteres multibyte sejam tratados corretamente por esta implementação. Para isso, é necessário primeiro converter corretamente os buffers em seqüências de caracteres e acompanhar os caracteres que são divididos entre dois buffers. Para fazer isso corretamente, pode-se usar o StringDecoder embutido
Ernelli
Enquanto isso, há uma maneira integrada de ler linhas de um arquivo, usando o readlinemódulo principal .
Dan Dascalescu 16/09
12

Com o módulo transportador :

var carrier = require('carrier');

process.stdin.resume();
carrier.carry(process.stdin, function(line) {
    console.log('got one line: ' + line);
});
Touv
fonte
Agradável. Isso também funciona para qualquer arquivo de entrada: var inStream = fs.createReadStream('input.txt', {flags:'r'}); Mas sua sintaxe é mais limpa que o método documentado de usar .on ():carrier.carry(inStream).on('line', function(line) { ...
Brent Faust
transportadora parece apenas lidar com \r\ne \nfinais de linha. Se você precisar lidar com arquivos de teste no estilo MacOS anteriores ao OS X, eles usaram o \rtransportador e não tratam disso. Surpreendentemente, ainda existem esses arquivos flutuando na natureza. Talvez você também precise manipular explicitamente a BOM Unicode (marca de ordem de bytes), usada no início dos arquivos de texto na esfera de influência do MS Windows.
hippietrail
Enquanto isso, há uma maneira integrada de ler linhas de um arquivo, usando o readlinemódulo principal .
Dan Dascalescu 16/09
9

Acabei com um vazamento de memória maciço usando o Lazy para ler linha por linha ao tentar processar essas linhas e gravá-las em outro fluxo devido à maneira como a drenagem / pausa / retomada no nó funciona (consulte: http: // elegantcode .com / 2011/04/06 / dando-passos-de-bebê-com-nó-js-bombeando-dados-entre-fluxos / (eu amo esse cara btw)). Eu não olhei atentamente o Lazy para entender exatamente o porquê, mas não pude pausar meu fluxo de leitura para permitir um dreno sem a saída do Lazy.

Eu escrevi o código para processar enormes arquivos csv em documentos xml, você pode ver o código aqui: https://github.com/j03m/node-csv2xml

Se você executar as revisões anteriores com a linha Lazy, ele vazará. A revisão mais recente não vaza e você provavelmente pode usá-la como base para um leitor / processador. Embora eu tenha algumas coisas personalizadas lá.

Edit: Acho que também devo observar que meu código com o Lazy funcionou bem até que me vi escrevendo fragmentos xml grandes o suficiente para drenar / pausar / retomar por necessidade. Para pedaços menores, tudo bem.

j03m
fonte
Enquanto isso, há uma maneira muito mais simples de ler linhas de um arquivo, usando o readlinemódulo principal .
Dan Dascalescu 16/09
Sim. Essa é a maneira correta agora. Mas isso foi em 2011. :)
j03m 16/09/15
8

Editar:

Use um fluxo de transformação .


Com um BufferedReader, você pode ler linhas.

new BufferedReader ("lorem ipsum", { encoding: "utf8" })
    .on ("error", function (error){
        console.log ("error: " + error);
    })
    .on ("line", function (line){
        console.log ("line: " + line);
    })
    .on ("end", function (){
        console.log ("EOF");
    })
    .read ();
Gabriel Llamas
fonte
1
Enquanto isso, há uma maneira muito mais simples de ler linhas de um arquivo, usando o readlinemódulo principal .
Dan Dascalescu 16/09
7

Desde que postei minha resposta original, descobri que a divisão é um módulo de nó muito fácil de usar para leitura de linha em um arquivo; O que também aceita parâmetros opcionais.

var split = require('split');
fs.createReadStream(file)
    .pipe(split())
    .on('data', function (line) {
      //each chunk now is a seperate line! 
    });

Não foi testado em arquivos muito grandes. Deixe-nos saber se você.

nf071590
fonte
6

Fiquei frustrado com a falta de uma solução abrangente para isso, então montei minha própria tentativa ( git / npm ). Lista de recursos copiados e colados:

  • Processamento de linha interativo (baseado em retorno de chamada, sem carregar o arquivo inteiro na RAM)
  • Opcionalmente, retorne todas as linhas em uma matriz (modo detalhado ou bruto)
  • Interrompa a transmissão interativamente ou execute o processamento de mapa / filtro
  • Detectar qualquer convenção de nova linha (PC / Mac / Linux)
  • Eof / tratamento de última linha correto
  • Manuseio correto de caracteres UTF-8 de vários bytes
  • Recuperar informações de deslocamento e comprimento de bytes por linha
  • Acesso aleatório, usando deslocamentos baseados em linhas ou em bytes
  • Mapeie automaticamente as informações de deslocamento de linha, para acelerar o acesso aleatório
  • Zero dependências
  • Testes

NIH? Você decide :-)

panta82
fonte
5
function createLineReader(fileName){
    var EM = require("events").EventEmitter
    var ev = new EM()
    var stream = require("fs").createReadStream(fileName)
    var remainder = null;
    stream.on("data",function(data){
        if(remainder != null){//append newly received data chunk
            var tmp = new Buffer(remainder.length+data.length)
            remainder.copy(tmp)
            data.copy(tmp,remainder.length)
            data = tmp;
        }
        var start = 0;
        for(var i=0; i<data.length; i++){
            if(data[i] == 10){ //\n new line
                var line = data.slice(start,i)
                ev.emit("line", line)
                start = i+1;
            }
        }
        if(start<data.length){
            remainder = data.slice(start);
        }else{
            remainder = null;
        }
    })

    stream.on("end",function(){
        if(null!=remainder) ev.emit("line",remainder)
    })

    return ev
}


//---------main---------------
fileName = process.argv[2]

lineReader = createLineReader(fileName)
lineReader.on("line",function(line){
    console.log(line.toString())
    //console.log("++++++++++++++++++++")
})
user531097
fonte
Vou testar isso, mas você pode me dizer, é garantido que nunca interrompa caracteres multibyte? (UTF-8 / UTF-16)
hippietrail
2
@hippietrail: A resposta é não para UTF-8, mesmo que esteja trabalhando em um fluxo de bytes em vez de um fluxo de caracteres. Ele quebra em novas linhas (0x0a). No UTF-8, todos os bytes de um caractere multibyte têm seu bit de ordem alta definido. Portanto, nenhum caractere multibyte pode incluir uma nova linha incorporada ou outro caractere ASCII comum. UTF-16 e UTF-32 são outra questão, no entanto.
1013 George
@ George: Acho que nos entendemos mal. Como CR e LF estão dentro do intervalo ASCII e o UTF-8 preserva os 128 caracteres ASCII inalterados, nem o CR nem o LF podem fazer parte de um caractere multibyte UTF-8. O que eu estava perguntando é se o datana chamada para stream.on("data")poder nunca começar ou terminar com apenas parte de um multibyte UTF-8 caracteres, como que é U+10D0, composta por três bytese1 83 90
hippietrail
1
Isso ainda carrega todo o conteúdo do arquivo na memória antes de torná-lo uma "nova linha". Isso não lê uma linha de cada vez; em vez disso, pega TODAS as linhas e as divide de acordo com o tamanho do buffer da "nova linha". Este método anula o objetivo de criar um fluxo.
297 Justin
Enquanto isso, há uma maneira muito mais simples de ler linhas de um arquivo, usando o readlinemódulo principal .
Dan Dascalescu 16/09
5

Eu queria resolver esse mesmo problema, basicamente o que em Perl seria:

while (<>) {
    process_line($_);
}

Meu caso de uso era apenas um script autônomo, não um servidor; portanto, síncrona estava correta. Estes foram os meus critérios:

  • O código síncrono mínimo que pode ser reutilizado em muitos projetos.
  • Não há limites para o tamanho do arquivo ou o número de linhas.
  • Não há limites para o comprimento das linhas.
  • Capaz de lidar com Unicode completo em UTF-8, incluindo caracteres além do BMP.
  • Capaz de lidar com as terminações de linha * nix e Windows (o Mac antigo não é necessário para mim).
  • Caracteres de terminações de linha a serem incluídos nas linhas.
  • Capaz de lidar com a última linha com ou sem caracteres de fim de linha.
  • Não use nenhuma biblioteca externa não incluída na distribuição node.js.

Este é um projeto para eu ter uma ideia do código de tipo de script de baixo nível no node.js e decidir o quão viável é como um substituto para outras linguagens de script como Perl.

Após uma quantidade surpreendente de esforço e algumas partidas falsas, esse é o código que eu criei. É bem rápido, mas menos trivial do que eu esperava: (bifurque no GitHub)

var fs            = require('fs'),
    StringDecoder = require('string_decoder').StringDecoder,
    util          = require('util');

function lineByLine(fd) {
  var blob = '';
  var blobStart = 0;
  var blobEnd = 0;

  var decoder = new StringDecoder('utf8');

  var CHUNK_SIZE = 16384;
  var chunk = new Buffer(CHUNK_SIZE);

  var eolPos = -1;
  var lastChunk = false;

  var moreLines = true;
  var readMore = true;

  // each line
  while (moreLines) {

    readMore = true;
    // append more chunks from the file onto the end of our blob of text until we have an EOL or EOF
    while (readMore) {

      // do we have a whole line? (with LF)
      eolPos = blob.indexOf('\n', blobStart);

      if (eolPos !== -1) {
        blobEnd = eolPos;
        readMore = false;

      // do we have the last line? (no LF)
      } else if (lastChunk) {
        blobEnd = blob.length;
        readMore = false;

      // otherwise read more
      } else {
        var bytesRead = fs.readSync(fd, chunk, 0, CHUNK_SIZE, null);

        lastChunk = bytesRead !== CHUNK_SIZE;

        blob += decoder.write(chunk.slice(0, bytesRead));
      }
    }

    if (blobStart < blob.length) {
      processLine(blob.substring(blobStart, blobEnd + 1));

      blobStart = blobEnd + 1;

      if (blobStart >= CHUNK_SIZE) {
        // blobStart is in characters, CHUNK_SIZE is in octets
        var freeable = blobStart / CHUNK_SIZE;

        // keep blob from growing indefinitely, not as deterministic as I'd like
        blob = blob.substring(CHUNK_SIZE);
        blobStart -= CHUNK_SIZE;
        blobEnd -= CHUNK_SIZE;
      }
    } else {
      moreLines = false;
    }
  }
}

Provavelmente poderia ser mais limpo, foi o resultado de tentativa e erro.

hippietrail
fonte
5

Na maioria dos casos, isso deve ser suficiente:

const fs = require("fs")

fs.readFile('./file', 'utf-8', (err, file) => {
  const lines = file.split('\n')

  for (let line of lines)
    console.log(line)
});
Dorian
fonte
2

Leitor de linha baseado em gerador: https://github.com/neurosnap/gen-readlines

var fs = require('fs');
var readlines = require('gen-readlines');

fs.open('./file.txt', 'r', function(err, fd) {
  if (err) throw err;
  fs.fstat(fd, function(err, stats) {
    if (err) throw err;

    for (var line of readlines(fd, stats.size)) {
      console.log(line.toString());
    }

  });
});
neurosnap
fonte
2

Se você quiser ler um arquivo linha por linha e escrever isso em outro:

var fs = require('fs');
var readline = require('readline');
var Stream = require('stream');

function readFileLineByLine(inputFile, outputFile) {

   var instream = fs.createReadStream(inputFile);
   var outstream = new Stream();
   outstream.readable = true;
   outstream.writable = true;

   var rl = readline.createInterface({
      input: instream,
      output: outstream,
      terminal: false
   });

   rl.on('line', function (line) {
        fs.appendFileSync(outputFile, line + '\n');
   });
};
Thami Bouchnafa
fonte
Qual é a diferença entre a sua e a resposta da kofrasa?
Buffalo
2
var fs = require('fs');

function readfile(name,online,onend,encoding) {
    var bufsize = 1024;
    var buffer = new Buffer(bufsize);
    var bufread = 0;
    var fd = fs.openSync(name,'r');
    var position = 0;
    var eof = false;
    var data = "";
    var lines = 0;

    encoding = encoding || "utf8";

    function readbuf() {
        bufread = fs.readSync(fd,buffer,0,bufsize,position);
        position += bufread;
        eof = bufread ? false : true;
        data += buffer.toString(encoding,0,bufread);
    }

    function getLine() {
        var nl = data.indexOf("\r"), hasnl = nl !== -1;
        if (!hasnl && eof) return fs.closeSync(fd), online(data,++lines), onend(lines); 
        if (!hasnl && !eof) readbuf(), nl = data.indexOf("\r"), hasnl = nl !== -1;
        if (!hasnl) return process.nextTick(getLine);
        var line = data.substr(0,nl);
        data = data.substr(nl+1);
        if (data[0] === "\n") data = data.substr(1);
        online(line,++lines);
        process.nextTick(getLine);
    }
    getLine();
}

Eu tive o mesmo problema e a solução acima parece semelhante a outras pessoas, mas é aSync e pode ler arquivos grandes muito rapidamente

Espero que isso ajude

user2056154
fonte
1

Eu tenho um pequeno módulo que faz isso bem e é usado por muitos outros projetos npm readline Observe que no nó v10 há um módulo nativo de readline, por isso republicou meu módulo como linebyline https://www.npmjs.com/package/ linha por linha

se você não quiser usar o módulo, a função é muito simples:

var fs = require('fs'),
EventEmitter = require('events').EventEmitter,
util = require('util'),
newlines = [
  13, // \r
  10  // \n
];
var readLine = module.exports = function(file, opts) {
if (!(this instanceof readLine)) return new readLine(file);

EventEmitter.call(this);
opts = opts || {};
var self = this,
  line = [],
  lineCount = 0,
  emit = function(line, count) {
    self.emit('line', new Buffer(line).toString(), count);
  };
  this.input = fs.createReadStream(file);
  this.input.on('open', function(fd) {
    self.emit('open', fd);
  })
  .on('data', function(data) {
   for (var i = 0; i < data.length; i++) {
    if (0 <= newlines.indexOf(data[i])) { // Newline char was found.
      lineCount++;
      if (line.length) emit(line, lineCount);
      line = []; // Empty buffer.
     } else {
      line.push(data[i]); // Buffer new line data.
     }
   }
 }).on('error', function(err) {
   self.emit('error', err);
 }).on('end', function() {
  // Emit last line if anything left over since EOF won't trigger it.
  if (line.length){
     lineCount++;
     emit(line, lineCount);
  }
  self.emit('end');
 }).on('close', function() {
   self.emit('close');
 });
};
util.inherits(readLine, EventEmitter);
Maleck13
fonte
1

Outra solução é executar a lógica por meio do executor seqüencial nsynjs . Ele lê o arquivo linha por linha usando o módulo readline do nó e não usa promessas ou recursão, portanto, não irá falhar em arquivos grandes. Aqui está como o código se parecerá:

var nsynjs = require('nsynjs');
var textFile = require('./wrappers/nodeReadline').textFile; // this file is part of nsynjs

function process(textFile) {

    var fh = new textFile();
    fh.open('path/to/file');
    var s;
    while (typeof(s = fh.readLine(nsynjsCtx).data) != 'undefined')
        console.log(s);
    fh.close();
}

var ctx = nsynjs.run(process,{},textFile,function () {
    console.log('done');
});

O código acima é baseado neste exemplo: https://github.com/amaksr/nsynjs/blob/master/examples/node-readline/index.js

amaksr
fonte
1

Duas perguntas que devemos nos fazer durante essas operações são:

  1. Qual a quantidade de memória usada para executá-lo?
  2. O consumo de memória está aumentando drasticamente com o tamanho do arquivo?

Soluções como require('fs').readFileSync()carregam o arquivo inteiro na memória. Isso significa que a quantidade de memória necessária para executar operações será quase equivalente ao tamanho do arquivo. Devemos evitá-los por algo maior que50mbs

Podemos rastrear facilmente a quantidade de memória usada por uma função colocando essas linhas de código após a chamada da função:

    const used = process.memoryUsage().heapUsed / 1024 / 1024;
    console.log(
      `The script uses approximately ${Math.round(used * 100) / 100} MB`
    );

Neste momento, a melhor maneira de ler linhas específicas de um arquivo grande está usando nó readline . A documentação tem exemplos incríveis .

Embora não precisemos de nenhum módulo de terceiros para fazer isso. Mas, se você estiver escrevendo um código corporativo, precisará lidar com muitos casos extremos. Eu tive que escrever um módulo muito leve chamado Apick File Storage para lidar com todos esses casos extremos .

Módulo Apick File Storage: https://www.npmjs.com/package/apickfs Documentação: https://github.com/apickjs/apickFS#readme

Arquivo de exemplo: https://1drv.ms/t/s!AtkMCsWInsSZiGptXYAFjalXOpUx

Exemplo: módulo de instalação

npm i apickfs
// import module
const apickFileStorage = require('apickfs');
//invoke readByLineNumbers() method
apickFileStorage
  .readByLineNumbers(path.join(__dirname), 'big.txt', [163845])
  .then(d => {
    console.log(d);
  })
  .catch(e => {
    console.log(e);
  });

Este método foi testado com sucesso com arquivos densos de até 4 GB.

big.text é um arquivo de texto denso com 163.845 linhas e tem 124 Mb. O script para ler 10 linhas diferentes desse arquivo usa aproximadamente apenas 4,63 MB apenas de memória. E analisa JSON válido para objetos ou matrizes gratuitamente. 🥳 impressionante !!

Podemos ler uma única linha do arquivo ou centenas de linhas do arquivo com muito pouco consumo de memória.

vivek agarwal
fonte
0

Eu uso isso:

function emitLines(stream, re){
    re = re && /\n/;
    var buffer = '';

    stream.on('data', stream_data);
    stream.on('end', stream_end);

    function stream_data(data){
        buffer += data;
        flush();
    }//stream_data

    function stream_end(){
        if(buffer) stream.emmit('line', buffer);
    }//stream_end


    function flush(){
        var re = /\n/;
        var match;
        while(match = re.exec(buffer)){
            var index = match.index + match[0].length;
            stream.emit('line', buffer.substring(0, index));
            buffer = buffer.substring(index);
            re.lastIndex = 0;
        }
    }//flush

}//emitLines

use esta função em um fluxo e ouça os eventos de linha que serão emitidos.

gr-

Elmer
fonte
0

Embora você provavelmente deva usar o readlinemódulo como a resposta principal sugere, readlineparece estar orientado para interfaces de linha de comando, e não para leitura de linha. Também é um pouco mais opaco em relação ao buffer. (Qualquer pessoa que precise de um leitor orientado a linhas de transmissão provavelmente desejará ajustar os tamanhos do buffer). O módulo readline possui ~ 1000 linhas, enquanto que, com estatísticas e testes, é 34.

const EventEmitter = require('events').EventEmitter;
class LineReader extends EventEmitter{
    constructor(f, delim='\n'){
        super();
        this.totalChars = 0;
        this.totalLines = 0;
        this.leftover = '';

        f.on('data', (chunk)=>{
            this.totalChars += chunk.length;
            let lines = chunk.split(delim);
            if (lines.length === 1){
                this.leftover += chunk;
                return;
            }
            lines[0] = this.leftover + lines[0];
            this.leftover = lines[lines.length-1];
            if (this.leftover) lines.pop();
            this.totalLines += lines.length;
            for (let l of lines) this.onLine(l);
        });
        // f.on('error', ()=>{});
        f.on('end', ()=>{console.log('chars', this.totalChars, 'lines', this.totalLines)});
    }
    onLine(l){
        this.emit('line', l);
    }
}
//Command line test
const f = require('fs').createReadStream(process.argv[2], 'utf8');
const delim = process.argv[3];
const lineReader = new LineReader(f, delim);
lineReader.on('line', (line)=> console.log(line));

Aqui está uma versão ainda mais curta, sem as estatísticas, em 19 linhas:

class LineReader extends require('events').EventEmitter{
    constructor(f, delim='\n'){
        super();
        this.leftover = '';
        f.on('data', (chunk)=>{
            let lines = chunk.split(delim);
            if (lines.length === 1){
                this.leftover += chunk;
                return;
            }
            lines[0] = this.leftover + lines[0];
            this.leftover = lines[lines.length-1];
            if (this.leftover) 
                lines.pop();
            for (let l of lines)
                this.emit('line', l);
        });
    }
}
javajosh
fonte
0
const fs = require("fs")

fs.readFile('./file', 'utf-8', (err, data) => {
var innerContent;
    console.log("Asynchronous read: " + data.toString());
    const lines = data.toString().split('\n')
    for (let line of lines)
        innerContent += line + '<br>';


});
Arindam
fonte
0

Eu envolvo toda a lógica do processamento diário de linha como um módulo npm: line-kit https://www.npmjs.com/package/line-kit

// example
var count = 0
require('line-kit')(require('fs').createReadStream('/etc/issue'),
                    (line) => { count++; },
                    () => {console.log(`seen ${count} lines`)})

Joyer
fonte
-1

Eu uso o código abaixo das linhas de leitura depois de verificar se não é um diretório e não está incluído na lista de arquivos não precisa ser verificado.

(function () {
  var fs = require('fs');
  var glob = require('glob-fs')();
  var path = require('path');
  var result = 0;
  var exclude = ['LICENSE',
    path.join('e2e', 'util', 'db-ca', 'someother-file'),
    path.join('src', 'favicon.ico')];
  var files = [];
  files = glob.readdirSync('**');

  var allFiles = [];

  var patternString = [
    'trade',
    'order',
    'market',
    'securities'
  ];

  files.map((file) => {
    try {
      if (!fs.lstatSync(file).isDirectory() && exclude.indexOf(file) === -1) {
        fs.readFileSync(file).toString().split(/\r?\n/).forEach(function(line){
          patternString.map((pattern) => {
            if (line.indexOf(pattern) !== -1) {
              console.log(file + ' contain `' + pattern + '` in in line "' + line +'";');
              result = 1;
            }
          });
        });
      }
    } catch (e) {
      console.log('Error:', e.stack);
    }
  });
  process.exit(result);

})();
Aniruddha Das
fonte
-1

Eu examinei todas as respostas acima, todas elas usam uma biblioteca de terceiros para resolvê-la. É ter uma solução simples na API do Node. por exemplo

const fs= require('fs')

let stream = fs.createReadStream('<filename>', { autoClose: true })

stream.on('data', chunk => {
    let row = chunk.toString('ascii')
}))
mrcode
fonte