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

553

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 quelques connexions pour que tout cela s'emboîte.

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

Le peu que je voudrais comprendre, c'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 que dans un pincement je pourrais revenir à utiliser quelque chose comme PHP, mais je voudrais comprendre cela.

Je ne pense pas que l'autre réponse fonctionnerait car le fichier est beaucoup plus volumineux que le serveur sur lequel je l'exécute dispose de mémoire.

Alex C
la source
2
Cela s'avère assez difficile à utiliser uniquement à bas niveau fs.readSync(). Vous pouvez lire des octets binaires dans un tampon, mais il n'y a pas de moyen facile de traiter les caractères UTF-8 ou UTF-16 partiels sans inspecter le tampon avant de le convertir en chaînes JavaScript et de rechercher les EOL. Le Buffer()type n'a pas un ensemble de fonctions aussi riche pour fonctionner sur ses instances que les chaînes natives, mais les chaînes natives ne peuvent pas contenir de données binaires. Il me semble que l'absence d'un moyen intégré de lire les lignes de texte à partir de descripteurs de fichiers arbitraires est une véritable lacune dans node.js.
hippietrail
5
Les lignes vides lues par cette méthode sont converties en une ligne contenant un seul 0 (code de caractère réel pour 0). J'ai dû pirater cette ligne là-dedans:if (line.length==1 && line[0] == 48) special(line);
Thabo
2
On pourrait également utiliser le package «ligne par ligne» qui fait parfaitement le travail.
Patrice
1
Veuillez mettre à jour la question pour dire que la solution consiste à utiliser un flux de transformation
Gabriel Llamas
2
@DanDascalescu si vous le souhaitez, vous pouvez l'ajouter à la liste: votre exemple a atterri légèrement modifié dans nodeles documents de l'API github.com/nodejs/node/pull/4609
eljefedelrodeodeljefe

Réponses:

790

Depuis Node.js v0.12 et à partir de Node.js v4.0.0, il existe un module de base readline stable . Voici la manière la plus simple de lire des lignes d'un fichier, sans aucun module externe:

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 bien:

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 ultérieure), même s'il n'y a pas de final \n.

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

Dan Dascalescu
la source
7
vous avez besoin d'un terminal: faux dans la définition de
createInterface
64
Comment déterminer la dernière ligne? En attrapant un événement "close":rl.on('close', cb)
Vert
27
Readline est dans un but similaire à GNU Readline , pas pour lire des fichiers ligne par ligne. Il y a plusieurs mises en garde pour l'utiliser pour lire des fichiers et ce n'est pas une meilleure pratique.
Nakedible
8
@Nakedible: intéressant. Pourriez-vous poster une réponse avec une meilleure méthode?
Dan Dascalescu
6
Je considère github.com/jahewson/node-byline comme la meilleure mise en œuvre de la lecture ligne par ligne, mais les opinions peuvent varier.
Nakedible
165

Pour une opération aussi simple, il ne devrait pas y avoir de dépendance vis-à-vis 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);
});
kofrasa
la source
33
malheureusement, cette solution attrayante ne fonctionne pas correctement - les lineévénements ne surviennent qu'après avoir frappé \n, c'est-à-dire que toutes les alternatives sont manquées (voir unicode.org/reports/tr18/#Line_Boundaries ). # 2, les données après la dernière \nsont silencieusement ignorées (voir stackoverflow.com/questions/18450197/… ). j'appellerais cette solution dangereuse car elle fonctionne pour 99% de tous les fichiers et pour 99% des données mais échoue silencieusement pour le reste. chaque fois que fs.writeFileSync( path, lines.join('\n'))vous avez écrit un fichier qui ne sera lu qu'en partie par la solution ci-dessus.
flow
4
Il y a un problème avec cette solution. Si vous utilisez your.js <lines.txt, vous n'obtiendrez pas la dernière ligne. S'il n'a pas de '\ n' à la fin du cours.
zag2art
Le readlinepackage se comporte de manière vraiment bizarre pour un programmeur Unix / Linux expérimenté.
Pointy
11
rd.on("close", ..); peut être utilisé comme rappel (se produit lorsque toutes les lignes sont lues)
Luca Steeb
6
Le problème des "données après le dernier \ n" semble être résolu dans ma version du nœud (0.12.7). Je préfère donc cette réponse, qui semble la plus simple et la plus élégante.
Myk Melez
63

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

fs.createReadStream

Passez ensuite ce flux à Lazy

Raynos
la source
2
Y a-t-il quelque chose comme un événement de fin pour Lazy? Lorsque toutes les lignes ont été lues?
Max
1
@Max, Try:new lazy(fs.createReadStream('...')).lines.forEach(function(l) { /* ... */ }).join(function() { /* Done */ })
Cecchi
6
@Cecchi et @Max, n'utilisez pas join car il mettra en mémoire tampon le fichier entier. Au lieu de cela, écoutez simplement l'événement «fin»:new lazy(...).lines.forEach(...).on('end', function() {...})
Corin
3
@Cecchi, @Corin et @Max: Pour ce que ça vaut, je me suis rendu fou d'enchaînement .on('end'... après .forEach(...) , alors qu'en fait tout s'est comporté comme prévu quand j'ai lié l'événement en premier .
crowjonah
52
Ce résultat est très élevé dans les résultats de recherche, il convient donc de noter que Lazy semble abandonné. Cela fait 7 mois sans aucun changement et a des bugs horribles (dernière ligne ignorée, fuites massives de mémoire, etc.).
blu
38

il y a un très joli module pour lire un fichier ligne par ligne, ça s'appelle lecteur de ligne

avec elle, 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 "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);
    });
  }
});
polaretto
la source
4
Cela fonctionne bien. Il lit même la dernière ligne (!). Il convient de mentionner qu'il conserve le \ r s'il s'agit d'un fichier texte de style Windows. line.trim () fait le tour de supprimer le \ r supplémentaire.
Pierre-Luc Bertrand
Il est sous-optimal dans la mesure où l'entrée ne peut provenir que d'un fichier nommé, et non (pour un exemple évident et extrêmement important process/stdin). Au moins, si c'est possible, ce n'est certainement pas évident en lisant le code et en le tentant.
Pointy
2
En attendant, il existe un moyen intégré de lire les lignes d'un fichier, en utilisant le readlinemodule principal .
Dan Dascalescu
C'est vieux, mais au cas où quelqu'un tomberait dessus function(reader)et function(line)devrait être:function(err,reader) et function(err,line).
jallmer
1
Pour mémoire, line-readerlit le fichier de manière asynchrone. L'alternative synchrone estline-reader-sync
Prajwal Dhatwalia
31
require('fs').readFileSync('file.txt', 'utf-8').split(/\r?\n/).forEach(function(line){
  console.log(line);
})
John Williams
la source
42
Cela lira le fichier entier en mémoire, puis le divisera en lignes. Ce n'est pas ce que les questions demandent. Le but est de pouvoir lire de gros fichiers séquentiellement, à la demande.
Dan Dascalescu du
2
Cela correspond à mon cas d'utilisation, je cherchais un moyen simple de convertir les entrées d'un script dans un autre format. Merci!
Callat
23

Mise à jour en 2019

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

Cela nécessite que le dernier Nodejs soit installé sur votre machine. > 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();
Développeur principal
la source
cette réponse est bien meilleure que tout ce qui précède grâce à son comportement basé sur la promesse, indiquant distinctement l'EOF.
phil294
Merci, c'est gentil.
Goran Stoyanov
3
Peut-être que cela est évident pour les autres, mais il m'a fallu un certain temps pour déboguer: si vous avez des awaits entre l' createInterface()appel et le début de la for awaitboucle, vous perdrez mystérieusement des lignes depuis le début du fichier. createInterface()commence immédiatement à émettre des lignes en arrière-plan, et l'itérateur asynchrone implicitement créé avec const line of rlne peut pas commencer à écouter ces lignes jusqu'à ce qu'il soit créé.
andrewdotn
19

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

Facile. Pas besoin de module externe.

nf071590
la source
2
Si vous obtenez readline is not definedou fs is not defined, ajoutez var readline = require('readline');et var fs = require('fs');pour que cela fonctionne. Sinon, doux, doux. Merci.
bergie3000
12
Cette réponse est une duplication exacte d'une réponse antérieure , mais sans les commentaires, le package readline est marqué comme instable (toujours instable en avril 2015) et, à la mi-2013, a eu du mal à lire les dernières lignes d'un fichier sans fin de ligne . Le dernier problème de ligne est apparu la première fois que je l'ai utilisé dans la v0.10.35, puis est parti. / argh
ruffin
Vous n'avez pas besoin de spécifier la sortie si vous ne faites que lire un flux de fichiers .
Dan Dascalescu
18

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

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 lorsque je travaillais sur un script d'analyse de journal rapide qui devait accumuler des données pendant l'analyse de journal et je pensais que ce 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 s'appuyer sur des modules tiers.Ainsi, après avoir lu toutes les réponses à cette question, chacun utilisant différents modules pour gérer l'analyse de ligne, une solution native 13 SLOC nodejs pourrait être intéressante.

Ernelli
la source
Il ne semble pas y avoir de moyen trivial d'étendre cela pour fonctionner avec des fichiers arbitraires, à part juste stdin... à moins que je manque quelque chose.
hippietrail
3
@hippietrail vous pouvez créer un ReadStreamavec fs.createReadStream('./myBigFile.csv')et l'utiliser au lieu destdin
nolith
2
Est-il garanti que chaque bloc ne contient que des lignes complètes? Les caractères UTF-8 multi-octets sont-ils garantis de ne pas être divisés aux limites des blocs?
hippietrail
1
@hippietrail Je ne pense pas que les caractères multi-octets soient correctement gérés par cette implémentation. Pour cela, il faut d'abord convertir correctement les tampons en chaînes et garder une trace des caractères répartis entre deux tampons. Pour le faire correctement, on peut utiliser le StringDecoder intégré
Ernelli
En attendant, il existe un moyen intégré de lire les lignes d'un fichier, en utilisant le readlinemodule principal .
Dan Dascalescu
12

Avec le module porteur :

var carrier = require('carrier');

process.stdin.resume();
carrier.carry(process.stdin, function(line) {
    console.log('got one line: ' + line);
});
Touv
la source
Agréable. Cela fonctionne également pour tout fichier d'entrée: var inStream = fs.createReadStream('input.txt', {flags:'r'}); Mais votre syntaxe est plus propre que la méthode documentée d'utilisation de .on ():carrier.carry(inStream).on('line', function(line) { ...
Brent Faust
le transporteur ne semble gérer \r\nque les \nfins de ligne. Si vous avez besoin de traiter des fichiers de test de style MacOS antérieurs à OS X, ils ont utilisé \ret l'opérateur ne gère pas cela. Étonnamment, il existe encore de tels fichiers dans la nature. Vous devrez peut-être également gérer explicitement la nomenclature Unicode (marque d'ordre des octets), elle est utilisée au début des fichiers texte dans la sphère d'influence MS Windows.
hippietrail
En attendant, il existe un moyen intégré de lire les lignes d'un fichier, en utilisant le readlinemodule principal .
Dan Dascalescu
9

Je me suis retrouvé avec une fuite de mémoire massive et massive en utilisant Lazy pour lire ligne par ligne lorsque j'essayais de traiter ces lignes et de les écrire dans un autre flux en raison de la façon dont drain / pause / resume dans le nœud fonctionne (voir: http: // elegcode .com / 2011/04/06 / prendre-pas-de-bébé-avec-noeud-js-pomper-les-données-entre-flux / (j'adore ce mec en passant)). Je n'ai pas regardé assez attentivement Lazy pour comprendre exactement pourquoi, mais je n'ai pas pu mettre en pause 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 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'aie 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 retrouve à écrire des fragments xml suffisamment grands qui drainent / s'arrêtent / reprennent à cause d'une nécessité. Pour les petits morceaux, c'était bien.

j03m
la source
En attendant, il existe un moyen beaucoup plus simple de lire les lignes d'un fichier, en utilisant le readlinemodule principal .
Dan Dascalescu
Ouaip. C'est la bonne façon maintenant. Mais c'était en 2011. :)
j03m
8

Éditer:

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 ();
Gabriel Llamas
la source
1
En attendant, il existe un moyen beaucoup plus simple de lire les lignes d'un fichier, en utilisant le readlinemodule principal .
Dan Dascalescu
7

Depuis la publication de ma réponse d'origine, j'ai découvert que split est un module de nœud très facile à utiliser pour la lecture de ligne dans un fichier; Qui accepte également des paramètres facultatifs.

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

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

nf071590
la source
6

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

  • Traitement de ligne interactif (basé sur le rappel, pas de chargement de tout le fichier dans la RAM)
  • Facultativement, renvoyez toutes les lignes dans un tableau (mode détaillé ou brut)
  • Interrompez le streaming de manière interactive ou effectuez un traitement de type carte / filtre
  • Détectez toute convention de nouvelle ligne (PC / Mac / Linux)
  • Traitement eof / last line correct
  • Gestion correcte 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, à l'aide de décalages basés sur des lignes ou des octets
  • Mappez automatiquement les informations de décalage de ligne pour accélérer l'accès aléatoire
  • Zéro dépendances
  • Les tests

NIH? Tu décides :-)

panta82
la source
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
la source
Je vais tester cela, mais pouvez-vous me dire, est-il garanti de ne jamais casser les caractères multi-octets? (UTF-8 / UTF-16)
hippietrail
2
@hippietrail: La réponse est non pour UTF-8, même s'il fonctionne sur un flux d'octets plutôt que sur un flux de caractères. Il casse sur les nouvelles lignes (0x0a). En UTF-8, tous les octets d'un caractère multi-octets ont leur bit de poids fort défini. Ainsi, aucun caractère multi-octets ne peut inclure une nouvelle ligne incorporée ou un autre caractère ASCII commun. UTF-16 et UTF-32 sont cependant une autre affaire.
George
@George: Je pense que nous nous comprenons mal. Comme CR et LF sont tous les deux dans la plage ASCII et UTF-8 conserve les 128 caractères ASCII inchangés, ni CR ni LF ne peuvent jamais faire partie d'un caractère UTF-8 multi-octets. Ce que je demandais, c'est si l' dataappel à stream.on("data")pourrait jamais commencer ou se terminer avec seulement une partie d'un caractère UTF-8 multi-octets tel que celui qui est U+10D0composé des trois octetse1 83 90
hippietrail
1
Cela charge toujours tout le contenu du fichier en mémoire avant d'en faire une "nouvelle ligne". Cela ne lit pas une ligne à la fois, il prend à la place TOUTES les lignes, puis les décompose en fonction de la longueur de la mémoire tampon "nouvelle ligne". Cette méthode va à l'encontre de l'objectif de création d'un flux.
Justin
En attendant, il existe un moyen beaucoup plus simple de lire les lignes d'un fichier, en utilisant le readlinemodule principal .
Dan Dascalescu
5

Je voulais aborder ce même problème, essentiellement ce que serait en 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 qui pourrait ê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 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 (un Mac à l'ancienne n'est pas nécessaire pour moi).
  • Caractère (s) 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 pour moi d'avoir une idée du 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 comme Perl.

Après un effort surprenant et quelques faux départs, voici le code que j'ai trouvé. C'est assez rapide mais moins trivial que ce à quoi je m'attendais: (forkez-le 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é davantage, c'était le résultat d'essais et d'erreurs.

hippietrail
la source
5

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)
});
Dorian
la source
2

Lecteur de ligne basé sur 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());
    }

  });
});
neurosnap
la source
2

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');
   });
};
Thami Bouchnafa
la source
Quelle est la différence entre la vôtre et la réponse de 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();
}

J'ai eu le même problème et j'ai trouvé la solution ci-dessus qui ressemble à d'autres, mais est aSync et peut lire des fichiers volumineux très rapidement

Espère que cela aide

user2056154
la source
1

J'ai un petit module qui le fait bien et qui est utilisé par un certain nombre d'autres projets npm readline Notez que dans le nœud v10, il y a un module natif readline, j'ai donc republié mon module en ligne par ligne https://www.npmjs.com/package/ ligne par ligne

si vous ne souhaitez 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);
Maleck13
la source
1

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 de lecture en ligne du nœud, et il n'utilise pas de promesses ou de récursivité, donc n'échouera pas sur les 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

amaksr
la source
1

Deux questions que nous devons nous poser lors de telles opérations sont:

  1. Quelle est la quantité de mémoire utilisée pour l'exécuter?
  2. La consommation de mémoire augmente-t-elle considérablement avec la taille du fichier?

Des solutions comme require('fs').readFileSync()charge tout le fichier en mémoire. Cela signifie que la quantité de mémoire requise pour effectuer des opérations sera presque équivalente à la taille du fichier. Nous devons les éviter pour tout ce qui dépasse50mbs

Nous pouvons facilement suivre la quantité de mémoire utilisée par une fonction en plaçant ces lignes de code après l'invocation de la fonction:

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

En ce moment , la meilleure façon de lire certaines lignes à partir d' un fichier volumineux utilise du nœud de readline . La documentation a des exemples étonnants .

Bien que nous n'ayons pas besoin d'un module tiers pour le faire. Mais, si vous écrivez un code d'entreprise, vous devez gérer de nombreux cas marginaux. J'ai dû écrire un module très léger appelé Apick File Storage pour gérer tous ces cas extrêmes.

Module de stockage de fichiers Apick: https://www.npmjs.com/package/apickfs Documentation: https://github.com/apickjs/apickFS#readme

Exemple de fichier: https://1drv.ms/t/s!AtkMCsWInsSZiGptXYAFjalXOpUx

Exemple: installer le module

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

Cette méthode a été testée avec succès jusqu'à 4 Go de fichiers denses.

big.text est un fichier texte dense avec 163 845 lignes et est de 124 Mo. Le script pour lire 10 lignes différentes de ce fichier utilise environ 4,63 Mo de mémoire seulement. Et il analyse gratuitement le JSON valide en objets ou tableaux. 🥳 Génial !!

Nous pouvons lire une seule ligne du fichier ou des centaines de lignes du fichier avec très peu de consommation de mémoire.

vivek agarwal
la source
0

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-

Elmer
la source
0

Bien que vous deviez probablement utiliser le readlinemodule comme le suggère la réponse du haut, il readlinesemble être orienté vers les interfaces de ligne de commande plutôt que vers la lecture de ligne. Il est également un peu plus opaque en ce qui concerne la mise en mémoire tampon. (Quiconque a besoin d'un lecteur orienté ligne de streaming voudra probablement modifier la taille des tampons). Le module readline fait ~ 1000 lignes alors que celui-ci, avec statistiques 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);
        });
    }
}
javajosh
la source
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
la source
0

J'enveloppe toute la logique du traitement de ligne quotidien comme un module npm: kit de ligne 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
la source
-1

J'utilise ci-dessous le code des lignes de lecture après avoir vérifié que ce n'est pas un répertoire et qu'il n'est pas inclus dans la liste des fichiers n'a pas besoin d'être vérifié.

(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
la source
-1

J'ai regardé toutes les réponses ci-dessus, toutes utilisent une bibliothèque tierce pour le résoudre. C'est une solution simple dans l'API de Node. par exemple

const fs= require('fs')

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

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