javascript read - Lire un fichier une ligne à la fois dans node.js?





file line (21)


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

J'essaie de lire un gros fichier 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 corresponde.

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

Le bit que je voudrais comprendre est comment je pourrais lire une ligne à la fois à partir d'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 rigueur je pourrais me rabattre sur l'utilisation de PHP, mais je voudrais comprendre cela.

Je ne pense pas que l'autre solution fonctionnerait car le fichier est beaucoup plus volumineux que le serveur pour lequel je l'utilise.




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

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

Mon cas d'utilisation était juste un script autonome, pas un serveur, donc synchrone était 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.
  • Pas de limites sur la longueur des lignes.
  • Capable de gérer un Unicode complet en UTF-8, y compris les caractères au-delà du BMP.
  • Capable de gérer les terminaisons de ligne nix et Windows (Mac ancien n'est pas nécessaire pour moi).
  • Caractère (s) des fins 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 pour moi d'avoir une idée du code de type de script de bas niveau dans node.js et de décider comment il est viable en remplacement d'autres langages de script comme Perl.

Après un nombre impressionnant d'efforts et quelques faux départs, c'est le code que j'ai trouvé. C'est plutôt rapide mais moins trivial que ce à quoi je m'attendais: (le fork 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.




Depuis que j'ai posté ma réponse originale, j'ai trouvé que split est un module de noeud très facile à utiliser pour la lecture de lignes dans un fichier; Qui accepte également les 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.




Je me suis retrouvé avec une énorme fuite de mémoire en utilisant Lazy pour lire ligne par ligne en essayant de traiter ces lignes et de les écrire dans un autre flux en raison de la façon dont fonctionne le drain (voir: http://elegantcode.com/2011/04/06/taking-baby-steps-with-node-js-pumping-data-between-streams/ (j'aime ce gars btw)). Je n'ai pas regardé assez attentivement Lazy pour comprendre exactement pourquoi, mais je ne pouvais pas mettre en pause mon flux de lecture pour permettre un vidage sans sortie Lazy.

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

Si vous exécutez les révisions précédentes avec la ligne Lazy, cela fuit. La dernière révision ne fuit 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 aussi noter que mon code avec Lazy a bien fonctionné jusqu'à ce que je me retrouve à écrire des fragments xml assez importants qui drainent / mettent en pause / reprennent parce qu'une nécessité. Pour les petits morceaux c'était bien.




J'étais frustré par l'absence d'une solution complète pour cela, alors j'ai monté ma propre tentative ( git / npm ). Liste des fonctionnalités copiées-collées:

  • Traitement de ligne interactif (basé sur le rappel, pas de chargement du fichier entier dans la RAM)
  • Facultativement, renvoyez toutes les lignes dans un tableau (mode détaillé ou brut)
  • Interrompez la diffusion en continu ou effectuez un traitement similaire à celui d'une carte ou d'un filtre
  • Détecter toute convention de nouvelle ligne (PC / Mac / Linux)
  • Correct eof / traitement de dernière ligne
  • Gestion correcte des caractères UTF-8 multi-octets
  • Récupérer les informations de décalage d'octet et de longueur d'octet par ligne
  • Accès aléatoire, utilisant des décalages basés sur des lignes ou des octets
  • Mapper automatiquement les informations de décalage de ligne pour accélérer l'accès aléatoire
  • Zéro dépendance
  • Tests

NIH? Tu décides :-)




Depuis Node.js v0.12 et depuis Node.js v4.0.0, il existe un module core readline stable. Voici la manière la 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 la version v0.12 ou ultérieure), même s'il n'y a pas de \n final.

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




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

  });
});



J'utilise ci-dessous le code les lignes de lecture après vérifier que ce n'est pas un répertoire et son non inclus dans la liste des fichiers ne doit pas être vérifier.

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

})();



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-




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



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("++++++++++++++++++++")
})



Ancien sujet, mais cela fonctionne:

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.




Vous n'avez pas à open le fichier, mais à la place, vous devez créer un ReadStream .

fs.createReadStream

Ensuite, passez ce flux à Lazy




Avec le module transporteur :

var carrier = require('carrier');

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



Modifier:

Utilisez un flux de transformation .

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



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 aSync et peut lire des fichiers volumineux très rapidement

Espère que cela aide




Une autre solution consiste à exécuter la logique via l'exécuteur séquentiel nsynjs . Il lit le fichier ligne par ligne à l'aide du module readline du noeud, et n'utilise pas de promesses ou de récursivité, ce qui évite d'échouer sur les gros fichiers. Voici comment le code ressemblera:

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




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


});



Vous pouvez toujours lancer votre propre lecteur de ligne. Je n'ai pas encore évalué cet extrait, mais il divise correctement le flux entrant de morceaux en lignes sans le \ 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();

J'ai trouvé cela en travaillant sur un script d'analyse rapide du journal qui devait accumuler des données pendant l'analyse du journal et j'ai pensé qu'il serait bien d'essayer de le faire en utilisant js et node au lieu d'utiliser perl ou bash.

Quoi qu'il en soit, je pense que les petits scripts nodejs devraient être autonomes et ne pas reposer sur des modules tiers. Après avoir lu toutes les réponses à cette question, en utilisant différents modules pour gérer l'analyse des lignes, une solution native peut être intéressante.




Alors que vous devriez probablement utiliser le module readline comme le suggère la meilleure réponse, readline semble être orienté vers les interfaces de ligne de commande plutôt que vers la lecture de lignes. C'est aussi un peu plus opaque concernant la mise en mémoire tampon. (Quiconque a besoin d'un lecteur orienté ligne de flux voudra probablement modifier les tailles de mémoire tampon). Le module readline est ~ 1000 lignes alors que celui-ci, avec stats et tests, est 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));

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



$ git log --diff-filter=D --summary  | grep "delete" | sort




javascript node.js file-io lazy-evaluation