web-dev-qa-db-ja.com

Node.jsでファイルを1行ずつ読み取る

大きなファイルを1行ずつ読み込もうとしています。私は Quoraに関する質問を見つけました /それは主題を扱いました、しかし、私は全体が一緒に合うようにするためにいくつかの関係を欠いています。

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

このサンプルのように、STDINの代わりにファイルから1行ずつ読み取る方法について説明します。

私は試した:

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

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

しかし、うまくいきません。私はピンチでPHPのようなものを使うことにフォールバックできることを知っていますが、私はこれを理解したいと思います。

私はそれを実行しているサーバーよりもファイルのサイズがはるかに大きいので、他の答えではうまくいかないと思います。

484
Alex C

Node.js v0.12以降およびNode.js v4.0.0以降では、stable readline coreモジュールがあります。外部モジュールなしでファイルから行を読み込む最も簡単な方法は次のとおりです。

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

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

最後の行は、最後の\nがなくても(Node v0.12以降で)正しく読み込まれます。

_ update _ :この例は NodeのAPI公式ドキュメントに追加されました

671
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);
});
155
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);
})
22
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
})

簡単です。外部モジュールは不要です。

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

ログ解析中にデータを蓄積する必要があるクイックログ解析スクリプトで作業したときにこれを思いついたのですが、Perlやbashの代わりにjsとnodeを使って試してみるのがいいと思いました。

とにかく、小さなnodejsスクリプトは自己完結型であり、サードパーティのモジュールに頼らないようにすべきだと思うので、それぞれが行解析を処理するためにさまざまなモジュールを使用するこの質問に対するすべての答えを読んだ後、1​​3 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

結局、ノード内のdrain/pause/resumeが動作する方法が原因で、それらの行を処理してそれらを別のストリームに書き込もうとすると、Lazyを使用して大量のメモリリークが発生しました(参照: http:/ /elegantcode.com/2011/04/06/taking-baby-steps-with-node-js-pumping-data-between-streams/ /(私はところでこの人が大好きです))。その理由を正確に理解するためにLazyを十分に詳しく調べていませんでしたが、Lazyを終了せずにドレインを可能にするために読み取りストリームを一時停止することはできませんでした。

私はxmlドキュメントに巨大なcsvファイルを処理するためのコードを書きました、あなたはここでコードを見ることができます: https://github.com/j03m/node-csv2xml

あなたがLazy lineを使って以前のリビジョンを実行すると、それはリークします。最新のリビジョンはまったくリークしていないので、おそらくそれをリーダー/プロセッサの基盤として使用することができます。私はそこにいくつかのカスタムものを持っていますが。

編集:私は自分が必要であるために排水/一時停止/再開する十分な大きさのxmlフラグメントを書いていることに気づくまで、私のLazyコードはうまくいったことにも注意すべきだと思います。小さい塊のためにそれは大丈夫だった。

8
j03m

私はこれに対する包括的な解決策がないことに不満を感じたので、私は自分自身の試みをまとめました( git / npm )。コピーペーストされた機能のリスト:

  • 対話型ライン処理(コールバックベース、ファイル全体をRAMにロードしない)
  • 必要に応じて、配列内のすべての行を返す(詳細モードまたは生モード)
  • 対話的にストリーミングを中断するか、またはマップ/フィルタのような処理を実行する
  • 改行規則を検出する(PC/Mac/Linux)
  • 正しいeof /最終行の扱い
  • マルチバイト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

ほとんどの場合、これで十分です。

const fs = require("fs")

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

  for (let line of lines)
    console.log(line)
});
5
Dorian
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($_);
}

私のユースケースは、サーバーではなく、単体のスクリプトだけだったので、同期は問題ありませんでした。これらが私の基準でした:

  • 多くのプロジェクトで再利用できる最小の同期コード。
  • ファイルサイズや行数に制限はありません。
  • 行の長さに制限はありません。
  • BMPを超える文字を含め、UTF-8で完全なUnicodeを処理することができます。
  • * nixとWindowsの行末を処理することができます(古いスタイルのMacは私には必要ありません)。
  • 行に含まれる行末文字。
  • 最後の行を行末文字の有無にかかわらず処理できます。
  • Node.jsディストリビューションに含まれていない外部ライブラリを使用しないでください。

これは私がnode.jsの低レベルのスクリプティングタイプのコードを理解し、それがPerlのような他のスクリプティング言語に代わるものとしてどれほど実行可能かを判断するためのプロジェクトです。

驚くべき努力と2、3の誤った開始の後、これは私が思い付いたコードです。それはかなり速いですが私が予想していたよりも自明ではありません: (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

ファイルを1行ずつ読み込み、それを別の行に書き込む場合は、次のようにします。

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モジュールを使用してファイルを行単位で読み取ります。また、promiseやrecursionを使用しないため、大きなファイルで失敗することはありません。コードは次のようになります。

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-kit https://www.npmjs.com/package/line-kit

// example
var count = 0
require('line-kit')(require('fs').createReadStream('/etc/issue'),
                    (line) => { count++; },
                    () => {console.log(`seen ${count} lines`)})
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