81

I'm writing a program in Node.js that (in some situations) wants to act as a simple filter: read everything from stdin (up to end of file), do some processing, write the result to stdout.

How do you do the 'read everything from stdin' part? The closest solutions I've found so far, seem to work either for one line at a time from the console, or else only work when stdin is a file not a pipe.

5 Answers 5

101

My boiler-plate for this one is a lot like the solution described in a comment above -- offering it at the top level because it's very much the simplest way to do this and it shouldn't be only in a comment.

var fs = require('fs');
var data = fs.readFileSync(0, 'utf-8');
// data is a string containing the file's contents
10
  • 51
    It might be more readable to do fs.readFileSync(process.stdin.fd, 'utf-8'); That way it will be clear to people reading that it is stdin. Commented Aug 24, 2019 at 10:08
  • 8
    @NicholasDaley, I get the following error when using your technique on Node v11.11.0: Error: EAGAIN: resource temporarily unavailable, read. The one in this answer still works ok though.
    – Sam
    Commented Sep 9, 2019 at 23:41
  • 3
    This will fail if you are piping a huge file into stdin
    – user949300
    Commented Mar 13, 2020 at 21:13
  • 6
    I'm getting an error on Windows when using this method: TypeError: Cannot read property '1' of undefined
    – szx
    Commented Apr 19, 2020 at 19:07
  • 2
    I spent hours troubleshooting this: THIS DOES NOT WORK ON WINDOWS if you pipe stdin to your program. If you want to know why, the rabbit hole starts here: github.com/aws/aws-cdk/issues/11314 Commented Aug 30, 2021 at 2:11
49

I use the following in Node 11+

 async function read(stream) {
   const chunks = [];
   for await (const chunk of stream) chunks.push(chunk); 
   return Buffer.concat(chunks).toString('utf8');
 }

Usage:

const input = await read(process.stdin);
5
  • 4
    Works with really long inputs
    – Cobertos
    Commented Oct 3, 2020 at 8:06
  • This isn't synchronous Commented Aug 30, 2021 at 2:16
  • Reading the chunks in as buffers and then concatenating them like this seems to work better than converting them to strings immediately: I was getting strange artifacts between the chunk points.
    – Gamma032
    Commented Jan 18, 2022 at 3:46
  • If the interactive input did not end in newline, I have to press Ctrl + D twice for the input to be used, I wonder if there's a solution to that. However this is the only method I can find that works for both pipes and interactive. Shame it is not synchronous, related: stackoverflow.com/questions/3430939/node-js-readsync-from-stdin Tested on v16.14.2, Ubuntu 22.10. Commented Mar 18, 2023 at 10:31
  • As of node v22, this is a good answer. Commented Jul 9 at 15:56
36

If you're on linux, there is no need for a 3rd party package for this. of course, consider your performance needs, but these two lines will work:

const fs = require("fs");
const data = fs.readFileSync("/dev/stdin", "utf-8");

Jan points out in the comments below that a more portable solution would be to use 0, as this is the POSIX standard. So, you may simple use:

const fs = require("fs");
const data = fs.readFileSync(0, "utf-8");

data is now a string with your data from stdin, interpreted as utf 8

3
  • 17
    Even better: use fs.readFileSync(0, 'utf8'), which should work everywhere. (File descriptor 0 is stdin).
    – Jan Schär
    Commented Sep 11, 2018 at 17:58
  • 3
    @Jan Schär, Doesn't work on Windows. (TypeError: Cannot read property '1' of undefined). But this does.
    – ikegami
    Commented Nov 16, 2020 at 20:57
  • 1
    Both '/dev/stdin' and 0 work on macOS.
    – knpwrs
    Commented Feb 11, 2022 at 22:22
12

get-stdin will do the trick.


A few notes reading between the lines in your question.

Since you tagged the question "synchronous" I'll just note that stdin is async-only in node.js. The above library is the simplest it gets. It will handle the entire input as either a string or a buffer.

If possible, writing your program in the streaming style is best, but some use cases are feasible for streaming (i.e. word count) and some are not (i.e. reverse the input).

Also the "one line at a time from the console" is an artifact of the terminal buffering your keystrokes. If you want some "I'm sorry I asked" level detail check out the amazing the TTY Demystified.

2
3

I haven't seen a solution here that is actually synchronous except for @Patrick Narkinsky's. But @Patrick Narkinsky's answer doesn't work on Windows. Seems to be a node.js bug. If you want to learn the details, feel free to go down this rabbit hole of github issues, but I gave up after an hour of reading.

  1. https://github.com/aws/aws-cdk/issues/11314 The issue reported here
  2. https://github.com/nodejs/node/issues/35997 A contributor from that library creates a node issue
  3. https://github.com/libuv/libuv/pull/3053 A nodejs contributor submits a PR with a fix(?) (not yet merged)

I wasn't able to find a workaround there (I probably glossed over it), but I accidentally stumbled on a solution to the problem. It's not pretty, but it works. Since that link only shows how to log the progress, I had to modify it for my own needs:

import fs from 'fs';
const BUFSIZE = 256;
const buf = Buffer.alloc(BUFSIZE);
let bytesRead;
let stdin = '';

export function stdinToString(): string {
  do {
    // Loop as long as stdin input is available.
    bytesRead = 0;
    try {
      bytesRead = fs.readSync(process.stdin.fd, buf, 0, BUFSIZE, null);
    } catch (e) {
      if (e.code === 'EAGAIN') {
        // 'resource temporarily unavailable'
        // Happens on OS X 10.8.3 (not Windows 7!), if there's no
        // stdin input - typically when invoking a script without any
        // input (for interactive stdin input).
        // If you were to just continue, you'd create a tight loop.
        throw 'ERROR: interactive stdin input not supported.';
      } else if (e.code === 'EOF') {
        // Happens on Windows 7, but not OS X 10.8.3:
        // simply signals the end of *piped* stdin input.
        break;
      }
      throw e; // unexpected exception
    }
    if (bytesRead === 0) {
      // No more stdin input available.
      // OS X 10.8.3: regardless of input method, this is how the end
      //   of input is signaled.
      // Windows 7: this is how the end of input is signaled for
      //   *interactive* stdin input.
      break;
    }
    // Process the chunk read.
    stdin += buf.toString(undefined, 0, bytesRead);
  } while (bytesRead > 0);

  return stdin;
}

I've been programming for over a decade and this is the first time a do while made my code cleaner :) Without it, this function would hang if no stdin data exists -- one could argue that was a bug in the code of that link.

This answers the original question AND works on all operating systems.

1
  • I'm still using the solution, but I stopped using it in one place. I can't remember exactly why, but I think it hangs if standard input is not passed in. There's probably a simple fix to the above code to deal with that, but I haven't looked into it Commented Feb 5, 2022 at 1:53

Not the answer you're looking for? Browse other questions tagged or ask your own question.