¿Leer un archivo una línea a la vez en node.js?

648

Estoy intentando leer un archivo grande una línea a la vez. Encontré una pregunta en Quora que trataba sobre el tema, pero me faltan algunas conexiones para que todo encaje.

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

Lo que me gustaría averiguar es cómo puedo leer una línea a la vez de un archivo en lugar de STDIN como en este ejemplo.

Lo intenté:

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

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

pero no funciona. Sé que en caso de apuro podría volver a usar algo como PHP, pero me gustaría resolver esto.

No creo que la otra respuesta funcione, ya que el archivo es mucho más grande de lo que tiene memoria el servidor en el que lo estoy ejecutando.

9
  • 3
    Esto resulta bastante difícil si se usa solo un nivel bajo fs.readSync(). Puede leer octetos binarios en un búfer, pero no hay una manera fácil de tratar con caracteres UTF-8 o UTF-16 parciales sin inspeccionar el búfer antes de traducirlo a cadenas de JavaScript y buscar EOL. El Buffer()tipo no tiene un conjunto de funciones tan completo para operar en sus instancias como las cadenas nativas, pero las cadenas nativas no pueden contener datos binarios. Me parece que la falta de una forma incorporada de leer líneas de texto de identificadores de archivos arbitrarios es una brecha real en node.js. 2 de enero de 2013 a las 1:52
  • 5
    Las líneas vacías leídas por este método se convierten en una línea con un solo 0 (código de carácter real para 0) en ellas. Tuve que piratear esta línea allí:if (line.length==1 && line[0] == 48) special(line);
    Thabo
    9 de agosto de 2013 a las 15:28
  • 2
    También se podría usar el paquete 'línea por línea' que hace el trabajo a la perfección.
    Patrice
    7 de febrero de 2014 a las 8:49
  • 1
    Actualice la pregunta para decir que la solución es usar una secuencia de transformación. 8 de junio de 2014 a las 6:33
  • 2
    @DanDascalescu si lo desea, puede agregar esto a la lista: su ejemplo aterrizó ligeramente modificado en nodelos documentos de la API github.com/nodejs/node/pull/4609 11/01/16 a las 19:47
947

Desde Node.js v0.12 y desde Node.js v4.0.0, hay un módulo principal de readline estable . Esta es la forma más sencilla de leer líneas de un archivo, sin 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();

O alternativamente:

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

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

La última línea se lee correctamente (a partir de Node v0.12 o posterior), incluso si no hay final \n.

ACTUALIZACIÓN : este ejemplo se ha agregado a la documentación oficial de la API de Node .

21
  • 7
    necesitas un terminal: falso en la definición de createInterface 17 de septiembre de 2015 a las 13:06
  • 71
    ¿Cómo determinar la última línea? Al detectar un evento "cercano":rl.on('close', cb)
    Green
    27 de septiembre de 2015 a las 16:04
  • 31
    Readline tiene un propósito similar al de GNU Readline , no para leer archivos línea por línea. Hay varias advertencias al usarlo para leer archivos y esta no es una buena práctica. 10/10/15 a las 11:42
  • 8
    @Nakedible: interesante. ¿Podrías publicar una respuesta con un método mejor? 14/10/15 a las 23:23
  • 8
    Considero que github.com/jahewson/node-byline es la mejor implementación de lectura línea por línea, pero las opiniones pueden variar. 15/10/15 a las 10:42
169

Para una operación tan simple, no debería haber ninguna dependencia de módulos de terceros. Con 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);
});
6
  • 35
    Lamentablemente, esta atractiva solución no funciona correctamente: los lineeventos se \nproducen solo después de presionar , es decir, se pierden todas las alternativas (consulte unicode.org/reports/tr18/#Line_Bo limits ). # 2, los datos posteriores al último \nse ignoran silenciosamente (consulte stackoverflow.com/questions/18450197/… ). Yo llamaría a esta solución peligrosa porque funciona para el 99% de todos los archivos y para el 99% de los datos, pero falla silenciosamente para el resto. siempre que lo haga fs.writeFileSync( path, lines.join('\n')), ha escrito un archivo que solo se leerá parcialmente con la solución anterior.
    flow
    27/0813 a las 15:56
  • 4
    Hay un problema con esta solución. Si usa your.js <lines.txt, no obtiene la última línea. Si no tiene un '\ n' al final, por supuesto.
    zag2art
    11 de enero de 2014 a las 12:31
  • El readlinepaquete se comporta de formas realmente extrañas para un programador experimentado de Unix / Linux.
    Pointy
    21/10/2014 a las 23:41
  • 12
    rd.on("close", ..); se puede usar como devolución de llamada (ocurre cuando se leen todas las líneas) 16 feb.15 a las 23:39
  • 6
    El problema de "datos después de la última \ n" parece estar resuelto en mi versión de node (0.12.7). Entonces prefiero esta respuesta, que parece la más simple y elegante. 1 de septiembre de 2015 a las 22:23
63

No es necesario que openel archivo, sino que debe crear un archivo ReadStream.

fs.createReadStream

Luego pasa esa corriente a Lazy

8
  • 2
    ¿Hay algo como un evento final para Lazy? ¿Cuando se hayan leído todas las líneas?
    Max
    15 de junio de 2011 a las 8:08
  • 1
    @Max, prueba: new lazy(fs.createReadStream('...')).lines.forEach(function(l) { /* ... */ }).join(function() { /* Done */ })
    Cecchi
    20/09/12 a las 19:39
  • 6
    @Cecchi y @Max, no use join porque almacenará todo el archivo en la memoria. En su lugar, solo escuche el evento 'final':new lazy(...).lines.forEach(...).on('end', function() {...})
    Corin
    17/10/12 a las 12:33
  • 3
    @Cecchi, @Corin y @Max: Por lo que vale, me volví loco encadenando .on('end'... después .forEach(...) , cuando en realidad todo se comportó como se esperaba cuando vinculé el evento primero . 6 de septiembre de 2013 a las 19:05
  • 54
    Este resultado es muy alto en los resultados de búsqueda, por lo que vale la pena señalar que Lazy parece abandonado. Han pasado 7 meses sin ningún cambio y tiene algunos errores horribles (última línea ignorada, fugas masivas de memoria, etc.).
    blu
    20/11/2013 a las 21:21
50
require('fs').readFileSync('file.txt', 'utf-8').split(/\r?\n/).forEach(function(line){
  console.log(line);
})
3
  • 53
    Esto leerá todo el archivo en la memoria y luego lo dividirá en líneas. No es lo que piden las preguntas. El punto es poder leer archivos grandes de forma secuencial, bajo demanda. 25/10/2013 a las 10:50
  • 5
    Esto se ajusta a mi caso de uso, estaba buscando una forma sencilla de convertir la entrada de un script a otro formato. ¡Gracias!
    Callat
    12/08/19 a las 15:56
  • 1
    Es posible que esto no responda a la pregunta original, pero sigue siendo útil si se ajusta a sus limitaciones de memoria. 29 de enero a las 16:32
45

Actualización en 2019

Un ejemplo impresionante ya está publicado en la documentación oficial de Nodejs. aquí

Esto requiere que la última versión de Nodejs esté instalada en su 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();
3
  • 3
    esta respuesta es mucho mejor que cualquier cosa anterior gracias a su comportamiento basado en promesas, que indica claramente el EOF.
    phil294
    4 de septiembre de 2019 a las 21:39
  • Gracias, eso es dulce. 23/10/19 a las 8:42
  • 6
    Tal vez esto sea obvio para otros, pero me tomó un tiempo depurarlo: si tiene algún mensaje de correo awaitelectrónico entre la createInterface()llamada y el inicio del for awaitciclo, misteriosamente perderá líneas desde el inicio del archivo. createInterface()inmediatamente comienza a emitir líneas detrás de escena, y el iterador asíncrono creado implícitamente con const line of rlno puede comenzar a escuchar esas líneas hasta que se crea. 9/11/19 a las 16:09
39

hay un módulo muy agradable para leer un archivo línea por línea, se llama lector de línea

con él simplemente escribe:

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

incluso puede iterar el archivo con una interfaz "estilo java", si necesita más control:

lineReader.open('file.txt', function(reader) {
  if (reader.hasNextLine()) {
    reader.nextLine(function(line) {
      console.log(line);
    });
  }
});
5
  • 4
    Esto funciona bien. Incluso lee la última línea (!). Vale la pena mencionar que mantiene la \ r si es un archivo de texto estilo Windows. line.trim () hace el truco de eliminar el \ r adicional. 4 mar 14 a las 18:41
  • Es subóptimo porque la entrada solo puede ser de un archivo con nombre, y no (para un ejemplo obvio y extremadamente importante process/stdin). Al menos, si puede, ciertamente no es obvio al leer el código e intentarlo.
    Pointy
    21/10/2014 a las 23:40
  • 2
    Mientras tanto, hay una forma incorporada de leer líneas de un archivo, utilizando el readlinemódulo principal . 16 de septiembre de 2015 a las 3:04
  • Esto es antiguo, pero en caso de que alguien se tope con él: function(reader)y function(line)debería ser: function(err,reader)y function(err,line).
    jallmer
    27/11/18 a las 13:50
  • 2
    Solo para el registro, line-readerlee el archivo de forma asincrónica. La alternativa sincrónica esline-reader-sync 25/03/19 a las 6:50
19

Tema antiguo, pero esto 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
})

Sencillo. No necesita módulo externo.

3
18

Siempre puede rodar su propio lector de líneas. Todavía no he comparado este fragmento, pero divide correctamente el flujo entrante de fragmentos en líneas sin el final '\ n'

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

Se me ocurrió esto cuando trabajaba en un script de análisis de registro rápido que necesitaba acumular datos durante el análisis de registro y sentí que sería bueno intentar hacer esto usando js y node en lugar de usar perl o bash.

De todos modos, creo que los scripts de nodejs pequeños deben ser independientes y no depender de módulos de terceros, por lo que después de leer todas las respuestas a esta pregunta, cada uno con varios módulos para manejar el análisis de líneas, una solución de nodejs nativa de 13 SLOC podría ser de interés.

6
  • No parece haber ninguna forma trivial de extender esto para que funcione con archivos arbitrarios además de solo stdin... a menos que me falte algo. 2 de enero de 2013 a las 1:55
  • 3
    @hippietrail puedes crear un ReadStreamcon fs.createReadStream('./myBigFile.csv')y usarlo en lugar destdin
    nolith
    8 de mayo de 2013 a las 12:57
  • 2
    ¿Se garantiza que cada fragmento contiene solo líneas completas? ¿Se garantiza que los caracteres UTF-8 de varios bytes no se dividirán en los límites de los fragmentos? 8 de mayo de 2013 a las 22:38
  • 1
    @hippietrail No creo que esta implementación maneje correctamente los caracteres multibyte. Para eso, primero se deben convertir correctamente los búferes en cadenas y realizar un seguimiento de los caracteres que se dividen entre dos búferes. Para hacer eso correctamente, uno puede usar el StringDecoder integrado
    Ernelli
    5 de diciembre de 2013 a las 9:07
  • Mientras tanto, hay una forma incorporada de leer líneas de un archivo, utilizando el readlinemódulo principal . 16 de septiembre de 2015 a las 3:04
12

Con el módulo portador :

var carrier = require('carrier');

process.stdin.resume();
carrier.carry(process.stdin, function(line) {
    console.log('got one line: ' + line);
});
3
  • Bonito. Esto también funciona para cualquier archivo de entrada: var inStream = fs.createReadStream('input.txt', {flags:'r'}); pero su sintaxis es más limpia que el método documentado de usar .on ():carrier.carry(inStream).on('line', function(line) { ... 1/01/12 a las 22:34
  • El portador parece solo manejar \r\ny \nterminar de línea. Si alguna vez necesita lidiar con archivos de prueba de estilo MacOS anteriores a OS X, ellos usaron \ry el operador no maneja esto. Sorprendentemente, todavía hay archivos de este tipo flotando en la naturaleza. También es posible que deba manejar la BOM Unicode (marca de orden de bytes) explícitamente, esto se usa al comienzo de los archivos de texto en la esfera de influencia de MS Windows. 31/12/12 a las 3:31
  • Mientras tanto, hay una forma incorporada de leer líneas de un archivo, utilizando el readlinemódulo principal . 16 de septiembre de 2015 a las 3:04
10

Terminé con una pérdida de memoria masiva y masiva usando Lazy para leer línea por línea cuando intentaba procesar esas líneas y escribirlas en otra secuencia debido a la forma en que funciona el drenaje / pausa / reanudación en el nodo (consulte: http: // elegantcode .com / 2011/04/06 / dar-pequeños-pasos-con-node-js-pumping-data-between-streams / (me encanta este tipo por cierto)). No he mirado lo suficiente a Lazy para entender exactamente por qué, pero no pude pausar mi flujo de lectura para permitir un drenaje sin que Lazy salga.

Escribí el código para procesar archivos csv masivos en documentos xml, puede ver el código aquí: https://github.com/j03m/node-csv2xml

Si ejecuta las revisiones anteriores con Lazy line, se filtra. La última revisión no se filtra en absoluto y probablemente pueda usarla como base para un lector / procesador. Aunque tengo algunas cosas personalizadas allí.

Editar: Supongo que también debería tener en cuenta que mi código con Lazy funcionó bien hasta que me encontré escribiendo fragmentos xml lo suficientemente grandes que drenan / pausan / reanudan por necesidad. Para trozos más pequeños estuvo bien.

2
  • Mientras tanto, hay una forma mucho más sencilla de leer líneas de un archivo, utilizando el readlinemódulo principal . 16 de septiembre de 2015 a las 3:01
  • sí. Esa es la forma correcta ahora. Pero esto fue de 2011. :)
    j03m
    16 de septiembre de 2015 a las 14:25
8

Editar:

Utilice una secuencia de transformación .


Con un BufferedReader puede leer líneas.

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 ();
1
7

Desde que publiqué mi respuesta original, descubrí que split es un módulo de nodo muy fácil de usar para leer líneas en un archivo; Que también acepta parámetros opcionales.

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

No he probado en archivos muy grandes. Háganos saber si lo hace.

7

En la mayoría de los casos, esto debería 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)
});
0
6

Estaba frustrado por la falta de una solución integral para esto, así que armé mi propio intento ( git / npm ). Lista de funciones copiada y pegada:

  • Procesamiento de línea interactivo (basado en devolución de llamada, sin cargar el archivo completo en la RAM)
  • Opcionalmente, devuelva todas las líneas en una matriz (modo detallado o sin formato)
  • Interrumpa la transmisión de forma interactiva o realice un procesamiento similar a un mapa / filtro
  • Detecta cualquier convención de nueva línea (PC / Mac / Linux)
  • Tratamiento correcto eof / última línea
  • Manejo correcto de caracteres UTF-8 multibyte
  • Recupere la información de desplazamiento de bytes y longitud de bytes por línea
  • Acceso aleatorio, usando compensaciones basadas en líneas o en bytes
  • Mapear automáticamente la información de desplazamiento de línea para acelerar el acceso aleatorio
  • Cero dependencias
  • Pruebas

¿NIH? Tú decides :-)

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("++++++++++++++++++++")
})
6
  • Lo probaré, pero ¿puede decirme si está garantizado que nunca romperá los caracteres multibyte? (UTF-8 / UTF-16) 2 de enero de 2013 a las 1:54
  • 2
    @hippietrail: la respuesta es no para UTF-8, a pesar de que está trabajando en un flujo de bytes en lugar de un flujo de caracteres. Se rompe en nuevas líneas (0x0a). En UTF-8, todos los bytes de un carácter multibyte tienen su conjunto de bits de orden superior. Por lo tanto, ningún carácter multibyte puede incluir una nueva línea incorporada u otro carácter ASCII común. Sin embargo, UTF-16 y UTF-32 son otro asunto.
    George
    10 de mayo de 2013 a las 4:40 p.m.
  • @George: Creo que nos entendemos mal. Como CR y LF están dentro del rango ASCII y UTF-8 conserva los 128 caracteres ASCII sin cambios, ni CR ni LF pueden formar parte de un carácter UTF-8 multibyte. Lo que estaba preguntando es si dataen la llamada a stream.on("data")podría comenzar o terminar con solo una parte de un carácter UTF-8 multibyte, como el que está U+10D0compuesto por los tres bytese1 83 90 10 de mayo de 2013 a las 6:19
  • 1
    Esto todavía carga todo el contenido del archivo en la memoria antes de convertirlo en una "nueva línea". Esto no LEE una línea a la vez, sino que toma TODAS las líneas y luego las divide de acuerdo con la longitud del búfer de "nueva línea". Este método frustra el propósito de crear una secuencia.
    Justin
    29/07/2015 a las 17:13
  • Mientras tanto, hay una forma mucho más sencilla de leer líneas de un archivo, utilizando el readlinemódulo principal . 16 de septiembre de 2015 a las 3:01
5

Quería abordar este mismo problema, básicamente lo que en Perl sería:

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

Mi caso de uso era solo un script independiente, no un servidor, por lo que la sincronización estaba bien. Estos fueron mis criterios:

  • El código sincrónico mínimo que podría reutilizarse en muchos proyectos.
  • No hay límites en el tamaño del archivo ni en el número de líneas.
  • No hay límites en la longitud de las líneas.
  • Capaz de manejar Unicode completo en UTF-8, incluidos los caracteres más allá del BMP.
  • Capaz de manejar * nix y finales de línea de Windows (no necesito Mac de estilo antiguo).
  • Caracteres finales de línea que se incluirán en las líneas.
  • Capaz de manejar la última línea con o sin caracteres de final de línea.
  • No use bibliotecas externas que no estén incluidas en la distribución de node.js.

Este es un proyecto para que me familiarice con el código de tipo de scripting de bajo nivel en node.js y decida qué tan viable es como reemplazo de otros lenguajes de scripting como Perl.

Después de una sorprendente cantidad de esfuerzo y un par de intentos fallidos, este es el código que se me ocurrió. Es bastante rápido pero menos trivial de lo que esperaba: (bifurque en 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;
    }
  }
}

Probablemente podría limpiarse más, fue el resultado de prueba y error.

0
3

Lector de línea basado en generador: 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());
    }

  });
});
2

Si desea leer un archivo línea por línea y escribirlo en otro:

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');
   });
};
1
  • ¿Cuál es la diferencia entre la respuesta tuya y la de kofrasa?
    Buffalo
    18/04/2017 a las 6:08
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();
}

Tuve el mismo problema y se me ocurrió la solución anterior que parece similar a los demás, pero es aSync y puede leer archivos grandes muy rápidamente

Espera que esto ayude

2

Dos preguntas que debemos hacernos al realizar este tipo de operaciones son:

  1. ¿Cuál es la cantidad de memoria utilizada para realizarlo?
  2. ¿Está aumentando drásticamente el consumo de memoria con el tamaño del archivo?

Soluciones como require('fs').readFileSync()cargar todo el archivo en la memoria. Eso significa que la cantidad de memoria necesaria para realizar operaciones será casi equivalente al tamaño del archivo. Deberíamos evitar estos para cualquier cosa más grande que50mbs

Podemos rastrear fácilmente la cantidad de memoria utilizada por una función colocando estas líneas de código después de la invocación de la función:

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

En este momento la mejor manera de leer las líneas particulares de un archivo de gran tamaño está utilizando el nodo de readline . La documentación tiene ejemplos asombrosos .

Aunque no necesitamos ningún módulo de terceros para hacerlo. Pero, si está escribiendo un código empresarial, debe manejar muchos casos extremos. Tuve que escribir un módulo muy liviano llamado Apick File Storage para manejar todos esos casos extremos .

Módulo de almacenamiento de archivos de Apick: https://www.npmjs.com/package/apickfs Documentación: https://github.com/apickjs/apickFS#readme

Archivo de ejemplo: https://1drv.ms/t/s!AtkMCsWInsSZiGptXYAFjalXOpUx

Ejemplo: instalar módulo

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 se probó con éxito con archivos densos de hasta 4 GB.

big.text es un archivo de texto denso con 163,845 líneas y 124 Mb. La secuencia de comandos para leer 10 líneas diferentes de este archivo utiliza aproximadamente solo 4,63 MB de memoria solamente. Y analiza JSON válido a objetos o matrices de forma gratuita. 🥳 ¡¡Impresionante !!

Podemos leer una sola línea del archivo o cientos de líneas del archivo con muy poco consumo de memoria.

1

Tengo un pequeño módulo que hace esto bien y es utilizado por muchos otros proyectos npm readline Tenga en cuenta que en el nodo v10 hay un módulo de readline nativo, así que volví a publicar mi módulo como linebyline https://www.npmjs.com/package/ linea por linea

si no desea utilizar el módulo, la función es muy simple:

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

Otra solución es ejecutar la lógica a través del ejecutor secuencial nsynjs . Lee el archivo línea por línea usando el módulo readline de nodo, y no usa promesas o recursividad, por lo tanto, no fallará en archivos grandes. Así es como se verá el código:

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

El código anterior se basa en este examen: https://github.com/amaksr/nsynjs/blob/master/examples/node-readline/index.js

1

Esta es mi forma favorita de revisar un archivo, una solución nativa simple para un archivo progresivo (como en una forma no "slurp" o todo en memoria) leído con modern async/await. Es una solución que encuentro "natural" al procesar archivos de texto grandes sin tener que recurrir al readlinepaquete ni a ninguna dependencia no básica.

let buf = '';
for await ( const chunk of fs.createReadStream('myfile') ) {
    const lines = buf.concat(chunk).split(/\r?\n/);
    buf = lines.pop();
    for( const line of lines ) {
        console.log(line);
    }
}
if(buf.length) console.log(buf);  // last line, if file does not end with newline

Puede ajustar la codificación en fs.createReadStreamo use chunk.toString(<arg>). Además, esto le permite ajustar mejor la división de línea a su gusto, es decir. utilícelo .split(/\n+/)para omitir líneas vacías y controlar el tamaño del fragmento con { highWaterMark: <chunkSize> }.

No olvide crear una función processLine(line)para evitar repetir el código de procesamiento de línea dos veces debido al final bufsobrante. Desafortunadamente, la ReadStreaminstancia no actualiza sus indicadores de fin de archivo en esta configuración, por lo que no hay forma, afaik, de detectar dentro del ciclo que estamos en la última iteración sin algunos trucos más detallados como comparar el tamaño del archivo de un fs.Stats()con .bytesRead. De ahí la bufsolución de procesamiento final , a menos que esté absolutamente seguro de que su archivo termina con una nueva línea \n, en cuyo caso el for awaitciclo debería ser suficiente.

★ Si prefiere la versión asincrónica de eventos, esta sería:

let buf = '';
fs.createReadStream('myfile')
.on('data', chunk => {
    const lines = buf.concat(chunk).split(/\r?\n/);
    buf = lines.pop();
    for( const line of lines ) {
        console.log(line);
    }
})
.on('end', () => buf.length && console.log(buf) );

★ Ahora, si no le importa importar el streampaquete principal, esta es la versión de flujo canalizado equivalente, que permite encadenar transformaciones como la descompresión gzip:

const { Writable } = require('stream');
let buf = '';
fs.createReadStream('myfile').pipe(
    new Writable({
        write: (chunk, enc, next) => {
            const lines = buf.concat(chunk).split(/\r?\n/);
            buf = lines.pop();
            for (const line of lines) {
                console.log(line);
            }
            next();
        }
    })
).on('finish', () => buf.length && console.log(buf) );
0

yo uso esto:

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 función en una secuencia y escuche los eventos de línea que se emitirán.

gramo-

0

Si bien probablemente debería usar el readlinemódulo como sugiere la respuesta principal, readlineparece estar orientado hacia interfaces de línea de comando en lugar de lectura de línea. También es un poco más opaco con respecto al almacenamiento en búfer. (Cualquiera que necesite un lector orientado a la línea de transmisión probablemente querrá ajustar el tamaño del búfer). El módulo readline es de ~ 1000 líneas, mientras que este, con estadísticas y pruebas, es de 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));

Aquí hay una versión aún más corta, sin las estadísticas, en 19 líneas:

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


});
0
0

Envuelvo toda la lógica del procesamiento diario de líneas como un 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`)})
-1

Utilizo el siguiente código para leer las líneas después de verificar que no es un directorio y que no está incluido en la lista de archivos que no es necesario verificar.

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

})();
-1

He revisado todas las respuestas anteriores, todas usan una biblioteca de terceros para resolverlo. Tiene una solución simple en la API de Node. p.ej

const fs= require('fs')

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

stream.on('data', chunk => {
    let row = chunk.toString('ascii')
}))
1
  • Supongo que los votos negativos porque esto no leerá todo el archivo a la vez, pero ¿cómo puede estar seguro de que cada fragmento termina con una nueva línea (\ n)? La lógica para verificar y almacenar líneas parciales no existe.
    YoniXw
    17 dic.20 a las 16:00