73

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.

Ciro Santilli OurBigBook.com
  • 347,512
  • 102
  • 1,199
  • 985
rwallace
  • 31,405
  • 40
  • 123
  • 242

5 Answers5

93

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 now points to a buffer containing the file's contents
Ciro Santilli OurBigBook.com
  • 347,512
  • 102
  • 1,199
  • 985
  • 47
    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. – Nicholas Daley-Okoye Aug 24 '19 at 10:08
  • 7
    @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 Sep 09 '19 at 23:41
  • 1
    @Sam yes, I reproduce when no pipe is used, just the terminal. I wonder how that is possible given that `process.stdin.fd === 0` seems to hold always. The problem also happens with `0` if `fs.stdin.fd` is merely accessed before, e.g.: `const fs = require('fs'); console.log(process.stdin.fd); console.log(fs.readFileSync(0, 'utf8'));` – Ciro Santilli OurBigBook.com Oct 08 '19 at 09:48
  • 3
    This will fail if you are piping a huge file into stdin – user949300 Mar 13 '20 at 21:13
  • 1
    `process.stdin.fd`, `"/dev/stdin"` and related qualifiers may not be as compatible with things like Deno for example... in these cases the `0` should be portable – Avindra Goolcharan Mar 29 '20 at 21:09
  • 6
    I'm getting an error on Windows when using this method: TypeError: Cannot read property '1' of undefined – szx Apr 19 '20 at 19:07
  • @szx, [This](https://stackoverflow.com/a/54565854/589924) does. – ikegami Nov 16 '20 at 20:57
  • Note that this does *not* work with `await fs.promises.readFile()` - *only* the synchronous method `fs.readFileSync()` works. – starbeamrainbowlabs Mar 29 '21 at 12:08
  • 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: https://github.com/aws/aws-cdk/issues/11314 – Daniel Kaplan Aug 30 '21 at 02:11
  • 1
    FYI about why `process.stdin.fd` doesn't always work: https://github.com/nodejs/node/issues/7439#issuecomment-228760636 – Andy Mar 24 '22 at 16:03
41

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);
qwtel
  • 4,467
  • 1
  • 18
  • 12
  • 4
    Works with really long inputs – Cobertos Oct 03 '20 at 08:06
  • This isn't synchronous – Daniel Kaplan Aug 30 '21 at 02: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 Jan 18 '22 at 03: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: https://stackoverflow.com/questions/3430939/node-js-readsync-from-stdin Tested on v16.14.2, Ubuntu 22.10. – Ciro Santilli OurBigBook.com Mar 18 '23 at 10:31
33

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

Avindra Goolcharan
  • 4,032
  • 3
  • 41
  • 39
  • 17
    Even better: use `fs.readFileSync(0, 'utf8')`, which should work everywhere. (File descriptor 0 is stdin). – Jan Schär Sep 11 '18 at 17:58
  • 3
    @Jan Schär, Doesn't work on Windows. (`TypeError: Cannot read property '1' of undefined`). But [this](https://stackoverflow.com/a/54565854/589924) does. – ikegami Nov 16 '20 at 20:57
  • 1
    Both `'/dev/stdin'` and `0` work on macOS. – knpwrs Feb 11 '22 at 22:22
13

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.

Peter Lyons
  • 142,938
  • 30
  • 279
  • 274
  • 12
    I would recommend against using this module, as [its author doesn't seem to understand what "standard input" really means](https://github.com/sindresorhus/get-stdin/issues/21), and is against fixing this deficiency. – Vladimir Panteleev Jul 31 '18 at 13:01
  • 4
    [get-stdin-with-tty](https://github.com/moos/get-stdin-with-tty) is a fork which appears to fix the problem. – Adam C Feb 01 '20 at 17:36
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.

Daniel Kaplan
  • 62,768
  • 50
  • 234
  • 356
  • 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 – Daniel Kaplan Feb 05 '22 at 01:53