วิธีอ่านจาก stdin ทีละบรรทัดในโหนด


177

ฉันต้องการประมวลผลไฟล์ข้อความที่มีโหนดโดยใช้การเรียกบรรทัดคำสั่งเช่น:

node app.js < input.txt

แต่ละบรรทัดของไฟล์จะต้องถูกประมวลผลแยกกัน แต่เมื่อประมวลผลแล้วบรรทัดที่ถูกลืมสามารถถูกลืมได้

ด้วยการใช้ฟังข้อมูลของ stdin ฉันได้รับไอน้ำอินพุตที่มีขนาดเป็นไบต์ดังนั้นฉันจึงตั้งค่านี้

process.stdin.resume();
process.stdin.setEncoding('utf8');

var lingeringLine = "";

process.stdin.on('data', function(chunk) {
    lines = chunk.split("\n");

    lines[0] = lingeringLine + lines[0];
    lingeringLine = lines.pop();

    lines.forEach(processLine);
});

process.stdin.on('end', function() {
    processLine(lingeringLine);
});

แต่ดูเหมือนว่าเลอะเทอะมาก ต้องนวดรอบรายการแรกและสุดท้ายของอาร์เรย์บรรทัด ไม่มีวิธีที่สง่างามกว่านี้อีกหรือ?

คำตอบ:


207

คุณสามารถใช้โมดูลreadlineเพื่ออ่านจาก stdin ทีละบรรทัด:

var readline = require('readline');
var rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout,
  terminal: false
});

rl.on('line', function(line){
    console.log(line);
})

3
ดูเหมือนว่าจะทำงานได้ดีสำหรับการป้อนข้อมูลด้วยมือในคอนโซลอย่างไรก็ตามเมื่อฉันส่งไฟล์ไปยังคำสั่งไฟล์จะถูกส่งไปยัง stdout แมลง? ขณะนี้ readline ถือว่าไม่เสถียร
Matt R. Wilson

1
ฉันคิดว่าคุณสามารถเปลี่ยนprocess.stdoutเป็นกระแสการเขียนที่แตกต่างกัน - มันอาจจะง่ายเหมือนoutput: new require('stream').Writable()
Jeff Sisson

3
น่าเสียดายที่ฉันต้องการ stdout ฉันทิ้งมันไว้จากคำถามของฉัน แต่ฉันพยายามทำให้แอปใช้งานnode app.js < input.txt > output.txtได้
Matt R. Wilson

เห็นได้ชัดว่านี่คือ 'การออกแบบ' github.com/joyent/node/issues/4243#issuecomment-10133900 ดังนั้นฉันจึงลงเอยด้วยการทำตามที่คุณพูดและให้ตัวเลือกการส่งออกเป็นกระแสข้อมูลแบบเขียนได้จริง ๆ จากนั้นเขียนไปยังกระแสข้อมูล stdout โดยตรง ฉันไม่ชอบ แต่ก็ใช้งานได้
Matt R. Wilson

13
ดูเหมือนว่าถ้าคุณผ่านการโต้แย้งterminal: falseเพื่อสร้างอินเทอร์เฟซจะแก้ไขปัญหานี้
jasoncrawford

61
// Work on POSIX and Windows
var fs = require("fs");
var stdinBuffer = fs.readFileSync(0); // STDIN_FILENO = 0
console.log(stdinBuffer.toString());

3
คุณสามารถรวมรายละเอียดบางอย่างได้ไหม มีคำตอบที่ได้รับการยอมรับเป็นอย่างมาก
jhhoff02

2
สิ่งนี้ใช้ไม่ได้สำหรับฉัน (โหนด v9.2.0, Windows) Error: EISDIR: illegal operation on a directory, fstat at tryStatSync (fs.js: 534: 13) `
AlexChaffee

2
ทำงานให้ฉันบนโหนด v6.11.2, OSX
tiffon

3
@AlexChaffee: มีปรากฏเป็นข้อผิดพลาดบน Windows (ปัจจุบันยังคงเป็นของ v9.10.1) ถ้าไม่มีการป้อนข้อมูล stdin หรือถ้า stdin ปิด - เห็นปัญหา GitHub นี้ นอกเหนือจากนี้โซลูชันยังทำงานบน Windows
mklement0

3
ทำงานได้ดีมากและสั้นที่สุดโดยไกลสามารถทำให้สั้นลงได้โดยทำfs.readFileSync(0).toString()
localhostdotdev

56

readlineถูกออกแบบมาเพื่อทำงานกับเทอร์มินัล (นั่นคือprocess.stdin.isTTY === true) มีจำนวนมากของโมดูลที่ให้แยกการทำงานสำหรับลำธารทั่วไปจะเหมือนแยก มันทำให้สิ่งต่าง ๆ เป็นเรื่องง่าย

process.stdin.pipe(require('split')()).on('data', processLine)

function processLine (line) {
  console.log(line + '!')
}

6
ไม่มันไม่ใช่. หากคุณไม่ต้องการอ่านทีละบรรทัดคุณไม่จำเป็นต้องใช้เลย
vkurchatkin

6
เคล็ดลับ: ถ้าคุณต้องการที่จะเรียกใช้โค้ดบางส่วนหลังจากการประมวลผลทุกสายเพิ่มหลังจากที่ครั้งแรก.on('end', doMoreStuff) .on()จำไว้ว่าถ้าคุณเพิ่งเขียนรหัสตามปกติหลังจากคำสั่งด้วย.on()รหัสนั้นจะทำงานก่อนที่จะอ่านอินพุตใด ๆ เนื่องจาก JavaScript ไม่ได้ซิงโครนัส
Rory O'Kane

14
#!/usr/bin/env node

const EventEmitter = require('events');

function stdinLineByLine() {
  const stdin = new EventEmitter();
  let buff = "";

  process.stdin
    .on('data', data => {
      buff += data;
      lines = buff.split(/[\r\n|\n]/);
      buff = lines.pop();
      lines.forEach(line => stdin.emit('line', line));
    })
    .on('end', () => {
      if (buff.length > 0) stdin.emit('line', buff);
    });

  return stdin;
}

const stdin = stdinLineByLine();
stdin.on('line', console.log);

0

แบ่งปันให้ผู้อื่น:

อ่านกระแสข้อมูลทีละบรรทัดควรจะดีสำหรับไฟล์ขนาดใหญ่ที่ส่งเข้าไปใน stdin เวอร์ชันของฉัน:

var n=0;
function on_line(line,cb)
{
    ////one each line
    console.log(n++,"line ",line);
    return cb();
    ////end of one each line
}

var fs = require('fs');
var readStream = fs.createReadStream('all_titles.txt');
//var readStream = process.stdin;
readStream.pause();
readStream.setEncoding('utf8');

var buffer=[];
readStream.on('data', (chunk) => {
    const newlines=/[\r\n]+/;
    var lines=chunk.split(newlines)
    if(lines.length==1)
    {
        buffer.push(lines[0]);
        return;
    }   

    buffer.push(lines[0]);
    var str=buffer.join('');
    buffer.length=0;
    readStream.pause();

    on_line(str,()=>{
        var i=1,l=lines.length-1;
        i--;
        function while_next()
        {
            i++;
            if(i<l)
            {
                return on_line(lines[i],while_next);
            }
            else
            {
                buffer.push(lines.pop());
                lines.length=0;
                return readStream.resume();
            }
        }
        while_next();
    });
  }).on('end', ()=>{
      if(buffer.length)
          var str=buffer.join('');
          buffer.length=0;
        on_line(str,()=>{
            ////after end
            console.error('done')
            ////end after end
        });
  });
readStream.resume();

-1

ในกรณีของฉันโปรแกรม (elinks) ส่งคืนบรรทัดที่ดูว่างเปล่า แต่อันที่จริงมีอักขระเทอร์มินัลพิเศษรหัสควบคุมสีและ backspace ดังนั้นgrepตัวเลือกที่แสดงในคำตอบอื่น ๆ ก็ไม่ได้ผลสำหรับฉัน ดังนั้นฉันจึงเขียนสคริปต์เล็ก ๆ นี้ใน Node.js ฉันเรียกไฟล์tightแต่นั่นเป็นเพียงชื่อสุ่ม

#!/usr/bin/env node

function visible(a) {
    var R  =  ''
    for (var i = 0; i < a.length; i++) {
        if (a[i] == '\b') {  R -= 1; continue; }  
        if (a[i] == '\u001b') {
            while (a[i] != 'm' && i < a.length) i++
            if (a[i] == undefined) break
        }
        else R += a[i]
    }
    return  R
}

function empty(a) {
    a = visible(a)
    for (var i = 0; i < a.length; i++) {
        if (a[i] != ' ') return false
    }
    return  true
}

var readline = require('readline')
var rl = readline.createInterface({ input: process.stdin, output: process.stdout, terminal: false })

rl.on('line', function(line) {
    if (!empty(line)) console.log(line) 
})
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.