it-roy-ru.com

Читайте файл по одной строке в node.js?

Я пытаюсь прочитать большой файл по одной строке за раз. Я нашел вопрос по Quora который касался темы, но мне не хватает некоторых связей, чтобы все это сошлось воедино.

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

Я хотел бы выяснить, как можно читать по одной строке за раз из файла вместо STDIN, как в этом примере.

Я старался: 

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

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

но это не работает Я знаю, что в крайнем случае я мог бы вернуться к использованию чего-то вроде PHP, но я хотел бы выяснить это. 

Я не думаю, что другой ответ будет работать, так как файл намного больше, чем у сервера, на котором он запущен, есть память. 

462
Alex C

Начиная с Node.js v0.12 и начиная с Node.js v4.0.0, существует стабильный readline core модуль. Вот самый простой способ чтения строк из файла без каких-либо внешних модулей:

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

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

Последняя строка читается правильно (начиная с Node v0.12 или более поздней), даже если нет окончательного \n.

UPDATE: этот пример был добавлен в официальную документацию API Node .

632
Dan Dascalescu

Для такой простой операции не должно быть никакой зависимости от сторонних модулей. Полегче.

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

Вам не нужно open файл, но вместо этого вы должны создать ReadStream.

fs.createReadStream

Затем передайте этот поток Lazy

63
Raynos

есть очень хороший модуль для чтения файла построчно, он называется line-reader

с этим вы просто пишете:

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

вы можете даже перебрать файл с интерфейсом в стиле Java, если вам нужно больше контроля:

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

Старая тема, но это работает:

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

Просто. Нет необходимости во внешнем модуле.

18
nf071590

Вы всегда можете свернуть свой собственный читатель строки. Я пока не тестировал этот фрагмент, но он правильно разбивает входящий поток фрагментов на строки без завершающего '\ 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();

Я придумал это, работая над сценарием быстрого анализа журнала, который должен был накапливать данные во время анализа журнала, и я чувствовал, что было бы неплохо попробовать сделать это, используя js и node вместо Perl или bash.

В любом случае, я чувствую, что небольшие скрипты nodejs должны быть автономными и не полагаться на сторонние модули, поэтому после прочтения всех ответов на этот вопрос, каждый из которых использует различные модули для обработки разборов строк, может оказаться интересным решение 13 SLOC для нативных nodejs.

18
Ernelli

С несущим модулем :

var carrier = require('carrier');

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

Правка:

Используйте преобразование потока .


С BufferedReader вы можете читать строки.

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

Я закончил с огромной утечкой памяти, используя Lazy для построчного чтения при попытке затем обработать эти строки и записать их в другой поток из-за того, как работает сток/пауза/резюме в узле (см .: http://elegantcode.com/2011/04/06/taking-baby-steps-with-node-js-pumping-data-between-streams/ (кстати, я люблю этого парня)). Я недостаточно внимательно посмотрел на Lazy, чтобы точно понять, почему, но я не мог приостановить поток чтения, чтобы обеспечить утечку без выхода из Lazy.

Я написал код для обработки больших CSV-файлов в XML-документы, вы можете увидеть код здесь: https://github.com/j03m/node-csv2xml

Если вы запускаете предыдущие ревизии с ленивой линией, она просачивается. Последняя редакция вообще не содержит утечек, и вы, вероятно, можете использовать ее как основу для считывателя/процессора. Хотя у меня там есть кое-что нестандартное. 

Правка: Я думаю, я должен также отметить, что мой код с Lazy работал нормально, пока я не обнаружил, что пишу достаточно большие фрагменты XML, которые истощают/пауза/резюме, потому что это необходимо. Для небольших кусков это было хорошо.

8
j03m

В большинстве случаев этого должно быть достаточно:

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

Я был разочарован отсутствием комплексного решения для этого, поэтому я собрал свою собственную попытку ( git / npm ). Вставленный в копию список функций:

  • Интерактивная обработка строки (на основе обратного вызова, без загрузки всего файла в оперативную память)
  • По желанию, вернуть все строки в массиве (подробный или необработанный режим)
  • Интерактивно прерывать потоковую передачу или выполнять обработку, подобную карте/фильтру
  • Обнаружение любого соглашения новой строки (ПК/Mac/Linux)
  • Правильное лечение eof/last line
  • Корректная обработка многобайтовых символов UTF-8
  • Получение информации о смещении байта и длине байта для каждой строки
  • Произвольный доступ с использованием линейных или байтовых смещений
  • Автоматически отображать информацию о смещении линии для ускорения произвольного доступа
  • Нулевые зависимости
  • Тесты

NIH? Вам решать :-)

6
panta82

После публикации моего исходного ответа я обнаружил, что split - очень простой в использовании модуль узла для чтения строк в файле; Который также принимает необязательные параметры. 

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

Не проверял на очень больших файлах. Дайте нам знать, если вы делаете.

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

Я хотел решить эту проблему, в основном то, что будет в Perl:

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

Мой вариант использования был просто автономным сценарием, а не сервером, поэтому синхронное было хорошо. Это были мои критерии:

  • Минимальный синхронный код, который можно использовать во многих проектах.
  • Нет ограничений на размер файла или количество строк.
  • Нет ограничений по длине линий.
  • Способен обрабатывать полный Unicode в UTF-8, включая символы за пределами BMP.
  • Умеет обрабатывать * nix и окончания строк Windows (Mac старого стиля мне не нужен).
  • Символы конца строки должны быть включены в строки.
  • Может обрабатывать последнюю строку с или без символов конца строки.
  • Не используйте внешние библиотеки, не включенные в дистрибутив node.js.

Это проект для меня, чтобы почувствовать низкоуровневый код типа сценариев в node.js и решить, насколько он жизнеспособен в качестве замены для других языков сценариев, таких как Perl.

После удивительного усилия и нескольких неудачных попыток, это код, который я придумал. Это довольно быстро, но менее тривиально, чем я ожидал: (раскошелиться на 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;
    }
  }
}

Вероятно, это можно было бы убрать и дальше, это было результатом проб и ошибок.

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

У меня была та же проблема, и я нашел решение, описанное выше .___. Похоже на другие, но является aSync и может очень быстро читать большие файлы.

Надеется, что это помогает

2
user2056154

Читатель линии на основе генератора: 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

Если вы хотите прочитать файл построчно и записать это в другом: 

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

Другое решение - запустить логику через последовательного исполнителя nsynjs . Он читает файл построчно, используя модуль readline узла, и не использует обещания или рекурсию, поэтому не собирается завершать работу с большими файлами. Вот как будет выглядеть код:

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

Код выше основан на этом экзамене: https://github.com/amaksr/nsynjs/blob/master/examples/node-readline/index.js

1
amaksr

Обновление в 2019 году

Прекрасный пример уже опубликован в официальной документации Nodejs. Вот

Для этого требуется, чтобы на вашем компьютере была установлена ​​последняя версия Nodejs. > 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

У меня есть небольшой модуль, который делает это хорошо и используется довольно многими другими проектами npm readline Обратите внимание, что в узле v10 есть собственный модуль readline, поэтому я переиздал свой модуль как linebyline https: // www. npmjs.com/package/linebyline

если вы не хотите использовать модуль, функция очень проста:

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

Я оборачиваю всю логику ежедневной обработки строки в виде модуля 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

я использую это:

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

используйте эту функцию в потоке и слушайте события линии, которые будут излучать.

gr-

0
Elmer

Хотя вам, вероятно, следует использовать модуль readline, как подсказывает главный ответ, readline, по-видимому, ориентирован на интерфейсы командной строки, а не на чтение строки. Это также немного более непрозрачно в отношении буферизации. (Любой, кому нужен читатель, ориентированный на потоковую линию, вероятно, захочет настроить размер буфера). Модуль readline составляет ~ 1000 строк, а со статистикой и тестами - 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));

Вот еще более короткая версия без статистики в 19 строк:

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