Suan
Suan

Reputation: 37085

node.js: How to detect an empty stdin stream?

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

Upvotes: 21

Views: 8812

Answers (5)

cupcakearmy
cupcakearmy

Reputation: 377

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.

Upvotes: 0

Cameron Tacklind
Cameron Tacklind

Reputation: 7204

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

Upvotes: 5

raine
raine

Reputation: 1920

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

Upvotes: 5

JD Isaacks
JD Isaacks

Reputation: 57974

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

Upvotes: 6

&#216;ystein Steimler
&#216;ystein Steimler

Reputation: 1617

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
$

Upvotes: 6

Related Questions