21

I have a node.js script/server that reads some input from stdin when its launched. However, sometimes there's no data to be passed in. This is troublesome because it seems like in this case neither the data nor end events are called. How can I detect when this is the case in the node.js code?

I'd like to avoid having to append special "end" characters at the end of the input, so as not to inconvenience the client. The associated code is below:

  var newHTML = '';
  var gfm = spawn(__dirname + '/node_modules/docter/bin/github-flavored-markdown.rb');
  process.stdin.on('data', function(chunk){
    gfm.stdin.write(chunk);
  });
  process.stdin.on('end', function(){
    gfm.stdin.end();
  });
  gfm.stdout.on('data', function(data) {
    newHTML += data;
  });
  gfm.on('exit',function(ecode){
    socket.emit('newContent', newHTML);
  });
  process.stdin.resume();
Suan
  • 34,563
  • 13
  • 47
  • 61
  • 1
    Have you tried process.stdin.on('error', function(){}) ? – Marshall Feb 10 '12 at 17:28
  • or gfm.stdout.on('error', function() {}) – Marshall Feb 10 '12 at 17:28
  • found a solution? that seems impossible. – antitoxic Jul 21 '13 at 14:32
  • 1
    This works perfectly for me on both Windows and Linux. I get the `end` event every single time, even when redirecting `stdin` from `/dev/null`. Could it be something else? On a somewhat related note, you should use the streams interface to pipe your `stdin` to `gfm`. – Nitzan Shaked Sep 06 '13 at 21:21
  • It seems to me that your github-flavored-markdown.rb script is blocking on a read from stdin, which would explain the behavior. – kberg Oct 11 '13 at 22:44
  • An answer to a similar question [is here](https://stackoverflow.com/a/15485424/26510) – Brad Parks Nov 07 '17 at 14:22

5 Answers5

6

An empty or no STDIN stream is detected by the end event from process.stdin.

This simple script stdin.js demonstrates that:

process.stdin.on( 'data', function(data) { console.log( data ) } );
process.stdin.on( 'end', function() { console.log( 'EOF' ) } );

Different scenarios:

$ echo test | node stdin.js
<Buffer 74 65 73 74 0a>
EOF
$ echo -n | node stdin.js
EOF
$ node stdin.js < /dev/null
EOF
$

This script pipe.js demonstrates that using pipe to a spawned child process works very well:

var spawn = require('child_process').spawn;
var cat = spawn( '/bin/cat' );
cat.stdout.on( 'data', function(data) { console.log( data ) } );
cat.stdout.on( 'end', function() { console.log( 'EOF' ) } );

process.stdin.pipe(cat.stdin);

As expected:

$ echo test | node pipe.js
<Buffer 74 65 73 74 0a>
EOF
$ echo -n | node pipe.js
EOF
$ node pipe.js < /dev/null
EOF
$
Øystein Steimler
  • 1,579
  • 1
  • 11
  • 11
  • 1
    Correct but there's a caveat, running simply `node pipe.js` will keep the program idle because there is not even an EOF character. More on [Cameron Tacklind's answer](https://stackoverflow.com/a/53347693/8186898) – Nino Filiu May 08 '20 at 14:30
6

I believe what may be happening is, you are not giving a stdin steam at all.

Øystein Steimler's example shows you feeding /dev/null into your app:

node pipe.js < /dev/null

However, never addressed when you do not stream stdin to the app at all. Just running node pipe.js will not exit because it is still waiting for stdin.

You can test this yourself with other unix programs, for example cat

Try running this:

cat < /dev/null

Now try just running:

cat

It does not exit because it is waiting for stdin. You can type into the terminal and send to the program by pressing enter. It will still not exit (and wait for more input) until it receives the EOF which you can do with ctrl+d

JD Isaacks
  • 56,088
  • 93
  • 276
  • 422
5

Something you can do is to make your app accept an argument like -s which makes it read from stdin.

That's what LiveScript's CLI tool does:

-s, --stdin read stdin

raine
  • 1,694
  • 17
  • 14
5

The way stdin works, what you want is not directly possible.

As others have pointed out, the end event will never trigger without something like < /dev/null to generate that EOF. Otherwise the program waits for the terminal to send a ^D.

An alternative approach that might work for you is to set a timeout that expires after a short time if there is no activity on stdin. This is less than ideal, but works:

function handleData(chunk) {
  //clearTimeout(timeout);
  gfm.stdin.write(chunk);
}
function handleEnd() {
  //clearTimeout(timeout);
  gfm.stdin.end();
}
function handleTimeout() {
  process.stdin.removeListener('data', handleData);
  process.stdin.removeListener('end', handleEnd);

  // Do whatever special handling is needed here.
  gfm.stdin.end();
}

const timeoutMilliseconds = 100;

process.stdin.on('data', handleData);
process.stdin.on('end', handleEnd);
const timeout = setTimeout(handleTimeout, timeoutMilliseconds);

// You could skip the rest of this code if you just add `clearTimeout(timeout)`
// to the body of `handleData` and `handleEnd` but that would call `clearTimeout` excessively.
function stopTimeout() {
  process.stdin.removeListener('data', stopTimeout)
  process.stdin.removeListener('end', stopTimeout);
  clearTimeout(timeout);
}

process.stdin.on('data', stopTimeout);
process.stdin.on('end', stopTimeout);
Cameron Tacklind
  • 5,764
  • 1
  • 36
  • 45
  • This did not work for me directly, and I had to call `process.stdin.destroy()` to actually exit. Tested on node v16.13.1 – Jang-hwan Kim Mar 23 '22 at 06:26
  • In my experience, while easy, using `.destroy()` shouldn't be necessary. IMHO, its an anti-pattern. Have you looked at the [`process.stdin` documentation](https://nodejs.org/api/process.html#processstdin)? It is a different type of stream depending on what it's connected to. Don't forget the [two reading modes](https://nodejs.org/api/stream.html#two-reading-modes). If it is `.resume()`d, it needs to be `.pause()`d to exit cleanly. – Cameron Tacklind Mar 23 '22 at 18:23
0

With timeouts and using promises you can get something nice going.

export function getStdin(timeout: number = 10): Promise<string> {
  return new Promise<string>((resolve, reject) => {
    // Store the data from stdin in a buffer
    let buffer = ''
    process.stdin.on('data', (d) => (buffer += d.toString()))

    // Stop listening for data after the timeout, otherwise hangs indefinitely
    const t = setTimeout(() => {
      process.stdin.destroy()
      resolve('')
    }, timeout)

    // Listen for end and error events
    process.stdin.on('end', () => {
      clearTimeout(t)
      resolve(buffer.trim())
    })
    process.stdin.on('error', reject)
  })
}

Basically you can wait until stdin is ended (which does not happen if left empty) OR set a timeout after which you consider stdin as empty.

cupcakearmy
  • 377
  • 1
  • 2
  • 9