web-dev-qa-db-fra.com

Lire un fichier ligne par ligne dans node.js?

J'essaie de lire un fichier volumineux, une ligne à la fois. J'ai trouvé une question sur Quora qui traitait du sujet, mais il me manque des liens pour que tout se passe bien.

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

Ce que j'aimerais comprendre, c'est comment lire une ligne à la fois dans un fichier au lieu de STDIN comme dans cet exemple.

J'ai essayé: 

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

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

mais ça ne marche pas. Je sais qu'à la limite je pourrais utiliser quelque chose comme PHP, mais j'aimerais comprendre ceci. 

Je ne pense pas que l’autre réponse fonctionnerait, car le fichier est beaucoup plus volumineux que le serveur sur lequel je l’utilise a de la mémoire. 

462
Alex C

Depuis Node.js v0.12 et depuis Node.js v4.0.0, il existe un module stable readline core. Voici le moyen le plus simple de lire les lignes d'un fichier, sans aucun module externe:

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

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

La dernière ligne est lue correctement (à partir de Node v0.12 ou version ultérieure), même s’il n’ya pas de \n final.

UPDATE: cet exemple a été ajouté à la documentation officielle de l'API de Node .

632
Dan Dascalescu

Pour une opération aussi simple, il ne devrait y avoir aucune dépendance à l'égard des modules tiers. Allez-y doucement.

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

Vous n'avez pas à open le fichier, mais vous devez plutôt créer une ReadStream.

fs.createReadStream

Puis passez ce flux à Lazy

63
Raynos

il existe un module très agréable pour lire un fichier ligne par ligne, il s'appelle line-reader

avec cela vous écrivez simplement:

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

vous pouvez même itérer le fichier avec une interface "de style Java", si vous avez besoin de plus de contrôle:

lineReader.open('file.txt', function(reader) {
  if (reader.hasNextLine()) {
    reader.nextLine(function(line) {
      console.log(line);
    });
  }
});
33
polaretto
require('fs').readFileSync('file.txt', 'utf-8').split(/\r?\n/).forEach(function(line){
  console.log(line);
})
20
John Williams

Vieux sujet, mais ça marche:

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

Simple. Pas besoin d'un module externe.

18
nf071590

Vous pouvez toujours lancer votre propre lecteur de ligne. Je n'ai pas encore analysé cet extrait, mais il divise correctement le flux entrant de morceaux en lignes sans le '\ n' suivi.

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

Je suis arrivé à cela lorsque je travaillais sur un script d’analyse rapide des journaux qui devait accumuler des données lors de l’analyse des journaux et j’ai pensé qu’il serait agréable d’essayer de le faire en utilisant js et node au lieu d’utiliser Perl ou bash.

Quoi qu’il en soit, j’estime que les petits scripts nodejs devraient être autonomes et ne pas dépendre de modules tiers. Ainsi, après avoir lu toutes les réponses à cette question, chacune utilisant divers modules pour gérer l’analyse de ligne, une solution nodej 13 native pour SLOC pourrait être intéressante.

18
Ernelli

Avec le module opérateur :

var carrier = require('carrier');

process.stdin.resume();
carrier.carry(process.stdin, function(line) {
    console.log('got one line: ' + line);
});
12
Touv

Modifier:

Utilisez un flux transform .


Avec BufferedReader , vous pouvez lire des lignes.

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 ();
8
Gabriel Llamas

Je me suis retrouvé avec une fuite de mémoire massive en utilisant Lazy pour lire ligne par ligne lorsque vous essayez de traiter ces lignes et de les écrire dans un autre flux en raison du fonctionnement de drain/pause/resume dans les nœuds (voir: http://elegantcode.com/2011/04/06/taking-baby-steps-with-node-js-pumping-data-between-streams/ (j'adore ce mec). Je n'ai pas suffisamment regardé Lazy pour comprendre pourquoi, mais je ne pouvais pas suspendre mon flux de lecture pour permettre un drain sans quitter Lazy.

J'ai écrit le code pour traiter des fichiers csv massifs en documents xml, vous pouvez voir le code ici: https://github.com/j03m/node-csv2xml

Si vous exécutez les révisions précédentes avec Lazy Line, cela fuit. La dernière révision ne coule pas du tout et vous pouvez probablement l'utiliser comme base pour un lecteur/processeur. Bien que j'ai des trucs personnalisés là-dedans. 

Edit: Je suppose que je devrais également noter que mon code avec Lazy a bien fonctionné jusqu'à ce que je me suis retrouvé à écrire des fragments xml assez volumineux qui drainent/mettent en pause/reprennent parce que c'est une nécessité. Pour les petits morceaux c'était bien.

8
j03m

Dans la plupart des cas, cela devrait suffire:

const fs = require("fs")

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

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

J'étais frustré par le manque de solution globale pour cela, alors j'ai monté ma propre tentative ( git / npm ). Liste de fonctionnalités copiées-collées:

  • Traitement de ligne interactif (basé sur le rappel, aucun chargement du fichier entier dans la RAM)
  • Éventuellement, renvoyer toutes les lignes d'un tableau (mode détaillé ou brut)
  • Interrompre la diffusion en continu de manière interactive ou effectuer un traitement de type carte/filtre
  • Détecter toute convention de nouvelle ligne (PC/Mac/Linux)
  • Traitement correct eof/last line
  • Traitement correct des caractères UTF-8 multi-octets
  • Récupérer les informations de décalage et de longueur d'octet par ligne
  • Accès aléatoire, utilisant des décalages basés sur des lignes ou des octets
  • Cartographier automatiquement les informations de décalage de ligne pour accélérer l'accès aléatoire
  • Zéro dépendance
  • Des tests

NIH? Tu décides :-)

6
panta82

Depuis la publication de ma réponse initiale, j’ai constaté que split est un module de nœud très facile à utiliser pour la lecture de lignes dans un fichier; Qui accepte également des paramètres optionnels. 

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

N'a pas testé sur de très gros fichiers. Faites-nous savoir si vous le faites.

6
nf071590
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("++++++++++++++++++++")
})
5
user531097

Je voulais aborder ce même problème, en gros ce que serait Perl:

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

Mon cas d'utilisation était juste un script autonome, pas un serveur, donc synchrone allait bien. Ce sont mes critères:

  • Le code synchrone minimal pouvant être réutilisé dans de nombreux projets.
  • Aucune limite sur la taille du fichier ou le nombre de lignes.
  • Aucune limite sur la longueur des lignes.
  • Capable de gérer le format Unicode complet dans UTF-8, y compris les caractères au-delà du BMP.
  • Capable de gérer * nix et les fins de ligne Windows (Mac ancien n'est pas nécessaire pour moi).
  • Caractères de fin de ligne à inclure dans les lignes.
  • Capable de gérer la dernière ligne avec ou sans caractères de fin de ligne.
  • N'utilisez aucune bibliothèque externe non incluse dans la distribution node.js.

C’est un projet qui me permet de comprendre le code de type de script de bas niveau dans node.js et de décider de sa viabilité en remplacement d’autres langages de script tels que Perl.

Après des efforts surprenants et quelques faux départs, voici le code que j'ai créé. C'est assez rapide mais moins trivial que ce à quoi je m'attendais: (à la fourchette sur 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;
    }
  }
}

Il pourrait probablement être nettoyé plus loin, c'était le résultat d'essais et d'erreurs.

4
hippietrail
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();
}

J'ai eu le même problème et est venu avec la solution ci-dessus .___. Ressemble à d'autres, mais est un Sync et peut lire des fichiers volumineux très rapidement

Espère que cela aide

2
user2056154

Lecteur de ligne basé sur un générateur: 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
neurosnap

Si vous voulez lire un fichier ligne par ligne et l'écrire dans un autre: 

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

Une autre solution consiste à exécuter la logique via l'exécuteur séquentiel nsynjs . Il lit les fichiers ligne par ligne à l'aide du module readline du nœud. Il n'utilise pas de promesses ni de récursivité. Il ne risque donc pas d'échouer sur des fichiers volumineux. Voici à quoi ressemblera le code:

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

Le code ci-dessus est basé sur cet exemple: https://github.com/amaksr/nsynjs/blob/master/examples/node-readline/index.js

1
amaksr

Mise à jour en 2019

Un excellent exemple est déjà publié sur la documentation officielle de Nodejs. ici

Cela nécessite que la dernière version de Nodejs soit installée sur votre ordinateur. > 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();
1
Lead Developer

J'ai un petit module qui le fait bien et qui est utilisé par pas mal de projets npm readline Note dans le noeud v10, il y a un module natif readline, j'ai donc republié mon module comme linebyline https://www.npmjs.com/package/linebyline

si vous ne voulez pas utiliser le module, la fonction est très 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
Maleck13

J'enveloppe toute la logique du traitement quotidien des lignes en tant que module npm: line-kithttps://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`)})

0
Joyer
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
Arindam

j'utilise ceci:

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

utilisez cette fonction sur un flux et écoutez les événements de ligne qui seront émis.

gr-

0
Elmer

Alors que vous devriez probablement utiliser le module readline comme le suggère la réponse principale, readline semble être orienté vers les interfaces de ligne de commande plutôt que vers la lecture de ligne. C'est aussi un peu plus opaque en ce qui concerne la mise en mémoire tampon. (Toute personne ayant besoin d’un lecteur orienté ligne en continu voudra probablement modifier la taille des tampons). Le module readline comprend environ 1 000 lignes, tandis que cela correspond à 34 statistiques et tests.

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

Voici une version encore plus courte, sans les statistiques, à 19 lignes:

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
javajosh