【问题标题】:How to read from stdin line by line in Node如何在节点中逐行读取标准输入
【发布时间】:2013-11-20 03:39:00
【问题描述】:

我希望使用如下命令行调用来处理带有节点的文本文件:

node app.js < input.txt

文件的每一行都需要单独处理,但是一旦处理,输入行可能会被忘记。

使用标准输入的数据监听器,我得到按字节大小分块的输入流,因此我进行了设置。

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

但这似乎太草率了。必须围绕线阵列的第一个和最后一个项目进行按摩。有没有更优雅的方式来做到这一点?

【问题讨论】:

    标签: node.js stdin


    【解决方案1】:

    您可以使用readline 模块逐行读取标准输入:

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

    【讨论】:

    • 这似乎很适合在控制台中手动输入输入,但是,当我将文件传递到命令中时,文件会发送到标准输出。一个错误?此时 readline 被认为是不稳定的。
    • 我认为您可以将process.stdout 更改为不同的可写流——它可以像output: new require('stream').Writable() 一样简单
    • 不幸的是,我需要标准输出。我把它排除在我的问题之外,但我试图让该应用程序可以用作 node app.js < input.txt > output.txt
    • 显然这是“设计使然”github.com/joyent/node/issues/4243#issuecomment-10133900。所以我最终按照你说的做了,并为输出选项提供了一个虚拟的可写流,然后直接写入标准输出流。我不喜欢它,但它有效。
    • 看起来如果你将参数terminal: false 传递给createInterface,它可以解决这个问题。
    【解决方案2】:
    // Work on POSIX and Windows
    var fs = require("fs");
    var stdinBuffer = fs.readFileSync(0); // STDIN_FILENO = 0
    console.log(stdinBuffer.toString());
    

    【讨论】:

    • 你能提供一些细节吗?已经有一个被高度接受的答案
    • 这对我不起作用(节点 v9.2.0,Windows)。 Error: EISDIR: illegal operation on a directory, fstat at tryStatSync (fs.js:534:13)`
    • @AlexChaffee:如果没有标准输入或标准输入已关闭,Windows 上似乎存在错误(自 v9.10.1 起仍然存在) - 请参阅 this GitHub issue。然而,除此之外,该解决方案确实适用于 Windows。
    • 效果很好,是迄今为止最短的,可以通过fs.readFileSync(0).toString()来缩短它
    • 请注意,“幻数”0 可以替换为更清晰的 process.stdin.fd(它只是硬编码为 0,但可以让您在做什么更明显)
    【解决方案3】:

    readline 专门设计用于终端(即process.stdin.isTTY === true)。有很多模块为通用流提供拆分功能,例如split。它让事情变得超级简单:

    process.stdin.pipe(require('split')()).on('data', processLine)
    
    function processLine (line) {
      console.log(line + '!')
    }
    

    【讨论】:

    • 不,不是。如果您不想逐行阅读,则根本不需要它
    • 提示:如果您想在处理完所有行后运行一些代码,请在第一个.on() 之后添加.on('end', doMoreStuff)。请记住,如果您只是在 .on() 语句之后正常编写代码,则该代码将在读取任何输入之前运行,因为 JavaScript 不是同步的。
    【解决方案4】:
    #!/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);
    

    【讨论】:

      【解决方案5】:

      逐行读取流,应该适合通过管道传输到标准输入的大文件,我的版本:

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

      解释:

      • 要在 utf8 字母上正确剪切它,而不是在中间字节集编码为 utf8,它确保它每次发出完整的多字节字母。
      • 当接收到数据时,输入暂停。它用于阻塞输入,直到所有行都用完为止。如果行处理函数比输入慢,它可以防止溢出。
      • 如果每次都有一行没有换行符。需要为所有呼叫累积它并且什么都不做,返回。一旦有不止一行,也追加它并使用累积缓冲区。
      • 在所有拆分的行都用完之后。在最后一行将最后一行推送到缓冲区并恢复暂停的流。

      es6 代码

      var n=0;
      async function on_line(line)
      {
          ////one each line
          console.log(n++,"line ",line);
          ////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', async (chunk) => {
          
          const newlines=/[\r\n]+/;
          var lines=chunk.split(newlines)
          if(lines.length==1)
          {
              buffer.push(lines[0]);
              return;
          }
          readStream.pause();
      
          // let i=0;
          buffer.push(lines[0]); // take first line
          var str=buffer.join('');
          buffer.length=0;//clear array, because consumed
          await on_line(str);
          
          for(let i=1;i<lines.length-1;i++)
             await on_line(lines[i]);
          buffer.push(lines[lines.length-1]);
          lines.length=0; //optional, clear array to hint GC.
          return readStream.resume();
        }).on('end', async ()=>{
            if(buffer.length)
                var str=buffer.join('');
                buffer.length=0;
                await on_line(str);
        });
        readStream.resume();
      

      es6代码我没有测试

      【讨论】:

      • 这个答案发生了什么?
      • @activedecay 添加了解释和es6代码
      【解决方案6】:

      在我的情况下,程序(elinks)返回的行看起来是空的,但实际上有特殊的终端字符、颜色控制代码和退格,所以其他答案中提供的 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) 
      })
      

      【讨论】:

        【解决方案7】:

        如果要先询问用户行数:

            //array to save line by line 
            let xInputs = [];
        
            const getInput = async (resolve)=>{
                    const readline = require('readline').createInterface({
                        input: process.stdin,
                        output: process.stdout,
                    });
                    readline.on('line',(line)=>{
                    readline.close();
                    xInputs.push(line);
                    resolve(line);
                    })
            }
        
            const getMultiInput = (numberOfInputLines,callback)=>{
                let i = 0;
                let p = Promise.resolve(); 
                for (; i < numberOfInputLines; i++) {
                    p = p.then(_ => new Promise(resolve => getInput(resolve)));
                }
                p.then(()=>{
                    callback();
                });
            }
        
            //get number of lines 
            const readline = require('readline').createInterface({
                input: process.stdin,
                output: process.stdout,
                terminal: false
            });
            readline.on('line',(line)=>{
                getMultiInput(line,()=>{
                   //get here the inputs from xinputs array 
                });
                readline.close();
            })

        【讨论】:

          【解决方案8】:
          process.stdin.pipe(process.stdout);
          

          【讨论】:

          • 请也添加一些解释
          • 嗨,阿尤什。感谢您的回答。通常那里更欢迎带有解释的答案。您想为您的答案添加解释吗?您也可以改进答案的格式。
          猜你喜欢
          • 2011-06-02
          • 1970-01-01
          • 2014-04-09
          • 1970-01-01
          • 1970-01-01
          • 2021-06-18
          • 2011-02-17
          相关资源
          最近更新 更多