262

I'm looking to process a text file with node using a command line call like:

node app.js < input.txt

Each line of the file needs to be processed individually, but once processed the input line can be forgotten.

Using the on-data listener of the stdin, I get the input steam chunked by a byte size so I set this up.

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

But this seems so sloppy. Having to massage around the first and last items of the lines array. Is there not a more elegant way to do this?

Eliran Malka
  • 15,821
  • 6
  • 77
  • 100
Matt R. Wilson
  • 7,268
  • 5
  • 32
  • 48

10 Answers10

310

You can use the readline module to read from stdin line by line:

const readline = require('readline');

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

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

rl.once('close', () => {
     // end of input
 });
Alexander Mills
  • 90,741
  • 139
  • 482
  • 817
levi
  • 23,693
  • 18
  • 59
  • 73
  • 6
    That seems to work well for entering input by hand in the console, however, when I pass a file into the command the file is sent to stdout. A bug? readline is considered unstable at this point. – Matt R. Wilson Nov 20 '13 at 04:11
  • 1
    I think you can just change `process.stdout` to a different writable stream — it could be as simple as `output: new require('stream').Writable()` – Jeff Sisson Nov 20 '13 at 04:45
  • 5
    Unfortunately, I need the stdout. I left it out of my question, but I'm trying to get the app to be usable as `node app.js < input.txt > output.txt`. – Matt R. Wilson Nov 20 '13 at 04:50
  • Apparently this is 'by design' https://github.com/joyent/node/issues/4243#issuecomment-10133900. So I ended up doing as you said and provided the output option a dummy writable stream, then wrote directly to the stdout stream. I don't like it, but it works. – Matt R. Wilson Nov 25 '13 at 20:11
  • 22
    Looks like if you pass the argument `terminal: false` to createInterface, it fixes this problem. – jasoncrawford Jul 08 '14 at 22:18
  • I have used `output: null` with no ill effects. – James McLeod Jan 13 '16 at 14:40
  • It's worth noting process.stdin is broken in electron for windows and the electron team does not believe it is fixable, so this code and every other example will not work. – user169771 May 08 '21 at 17:12
  • reading from stdin is common practice for utilities and cli programs... it should definitely not require an external module/dependency – tmillr Aug 13 '22 at 09:24
  • readline is a builtin module to node. Also it makes sense because javascript was designed for browsers which don't have consoles (in the traditional sense). – Griffork Oct 05 '22 at 20:32
137
// Work on POSIX and Windows
var fs = require("fs");
var stdinBuffer = fs.readFileSync(0); // STDIN_FILENO = 0
console.log(stdinBuffer.toString());
Sir Athos
  • 9,403
  • 2
  • 22
  • 23
Zealic
  • 1,535
  • 1
  • 10
  • 6
  • 4
    Could you include some details? There is already a highly rated accepted answer – jhhoff02 Aug 03 '17 at 14:35
  • 3
    This doesn't work for me (node v9.2.0, Windows). `Error: EISDIR: illegal operation on a directory, fstat at `tryStatSync (fs.js:534:13)` – AlexChaffee Dec 18 '17 at 22:47
  • 2
    Worked for me on node v6.11.2, OSX. – tiffon Jan 27 '18 at 18:44
  • 5
    @AlexChaffee: There appears to be a bug on Windows (still present as of v9.10.1) if there's no stdin input or if stdin is closed - see [this GitHub issue](https://github.com/nodejs/node/issues/19831). Apart from this, however, the solution _does_ work on Windows. – mklement0 Apr 05 '18 at 13:48
  • 1
    This is what I was looking for, but it doesn't actually answer the question, so I guess it's kind of a wash. – Dave Oct 05 '18 at 17:34
  • for whom got the fs not defined error: https://www.w3schools.com/nodejs/nodejs_filesystem.asp – KenIchi Apr 08 '19 at 02:21
  • 8
    works very well and is the shortest by far, could make it shorter by doing `fs.readFileSync(0).toString()` – localhostdotdev May 13 '19 at 01:01
  • Great answer! Really helps for js shell scripts and quickly/easily/synchronously getting values piped into the script as input. – Robin Oct 26 '20 at 19:20
  • 19
    Note that the "magic number" 0 can be replaced with the clearer `process.stdin.fd` (which is just hard-coded to 0 but makes it more obvious what you're doing) – Dave Nov 06 '20 at 20:58
  • 2
    my note: `echo hoge | node -e 'console.log(require("fs").readFileSync(0).toString())'` – ー PupSoZeyDe ー May 30 '21 at 05:55
  • 2
    Nice, and does not require external dependencies and setting up npm package. Hence, can be used in inline mode from anywhere. – Sohail Si Jan 28 '22 at 19:41
  • This did not work for work on Windows 11 when stdin is long. Truncated the input. – Tarnay Kálmán Apr 17 '23 at 12:49
69

readline is specifically designed to work with terminal (that is process.stdin.isTTY === true). There are a lot of modules which provide split functionality for generic streams, like split. It makes things super-easy:

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

function processLine (line) {
  console.log(line + '!')
}
yolenoyer
  • 8,797
  • 2
  • 27
  • 61
vkurchatkin
  • 13,364
  • 2
  • 47
  • 55
  • 7
    no it's not. If you don't want to read line-by-line you don't need it at all – vkurchatkin Dec 17 '14 at 15:56
  • 9
    Tip: if you want to run some code after processing all the lines, add `.on('end', doMoreStuff)` after the first `.on()`. Remember that if you just write the code normally after the statement with `.on()`, that code will run before any input is read, because JavaScript isn’t synchronous. – Rory O'Kane Jan 23 '16 at 10:31
23
#!/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);
MikeMike
  • 675
  • 5
  • 9
simonepri
  • 493
  • 6
  • 11
3

New answer to old question.

Since Node 10 (April 2018) ReadableStreams such as process.stdin support for-await-of loops thanks to the addition of a Symbol.asyncIterator method (ReadableStream documentation, Symbol.asyncIterator documentation).

Using this we can create an adaptor that goes from iterating through chunks of data, to iterating through lines. The logic for doing this was adapted from this answer.

function streamByLines(stream) {
  stream.setEncoding('utf8');
  return {
    async *[Symbol.asyncIterator]() {
      let buffer = '';

      for await (const chunk of stream) {
        buffer += chunk;
        const lines = buffer.split(/\r?\n/);
        buffer = lines.pop();
        for (const line of lines) {
          yield line;
        }
      }
      if (buffer.length > 0) yield buffer;
    },
  };
}

You can use it like this (in a context where await is allowed)

for await (const line of streamByLines(process.stdin)) {
  console.log('Current line:', line)
}
Kevin Peña
  • 712
  • 3
  • 10
1

Node.js has changed a lot since the accepted answer was posted, so here is a modern example using readline to split the stream into lines, for await to read from the stream, and ES modules:

import { createInterface } from "node:readline"

for await (const line of createInterface({ input: process.stdin })) {
  // Do something with `line` here.
  console.log(line)
}
Thai
  • 10,746
  • 2
  • 45
  • 57
0

read stream line by line,should be good for large files piped into stdin, my version:

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

Explanation:

  • to cut it correctly on utf8 letter and not in middle byte set encoding to utf8 it ensures it emits each time full multibyte letter.
  • When data is received the input is paused. It is used to block the input until all lines are used up. It prevents overflowing the buffet if the lines processing function is slower than input.
  • If there is every time a line without newlines each time. need to accommulate it for all calls and do nothing, return . once there are more than one line also append it and use the accommulated buffer.
  • after all the splitted lines were consumed. On the last line push the last line to buffer and resume paused stream.

es6 code

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

I did not test the es6 code

Shimon Doodkin
  • 4,310
  • 34
  • 37
-1

In my case the program (elinks) returned lines that looked empty, but in fact had special terminal characters, color control codes and backspace, so grep options presented in other answers did not work for me. So I wrote this small script in Node.js. I called the file tight, but that's just a random name.

#!/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) 
})
exebook
  • 32,014
  • 33
  • 141
  • 226
-2

if you want to ask the user number of lines first:

    //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();
    })
Amr
  • 433
  • 5
  • 12
-9
process.stdin.pipe(process.stdout);
Dharman
  • 30,962
  • 25
  • 85
  • 135