nodejs / node

Node.js JavaScript runtime ✨🐢🚀✨
https://nodejs.org
Other
107.46k stars 29.55k forks source link

No event on child-process stdout #2926

Closed ctag closed 9 years ago

ctag commented 9 years ago

I'd like to have a nodejs script interact with a child process via standard input and output. I've tried setting this up with what the documentation provides, but I've arrived at a deadlock where what's happening doesn't match what's I believe is documented.

What I see in the documentation:

The ChildProcess class is not intended to be used directly. Use the spawn(), exec(), execFile(), or fork() methods to create a Child Process instance.

Reading each section leads me to:

The section for spawn describes access to an stdio option. Looking further up the page, I read that the child's stdin and stdout both default to a writeable or readable stream.

OK, I don't entirely know what a stream is in the context of Node, so looking at the stream doc page:

Readable streams have two "modes": a flowing mode and a paused mode. When in flowing mode, data is read from the underlying system and provided to your program as fast as possible.

Cool, and then since I've already looked at the child-process.spawn examples, I know that .on('data'... is important. It's listed right below:

Attaching a data event listener to a stream that has not been explicitly paused will switch the stream into flowing mode. Data will then be passed as soon as it is available. If you just want to get all the data out of the stream as fast as possible, this is the best way to do so.

This looks pretty straightforward, I want spawn. This child process of mine will persist for a while, and I want access to the output as it appears. Sounds like a good match.

Running the provided example:

Great, time to become familiar with the .spawn operator. To do so, I'll run the provided ps ax | grep ssh example:

var spawn = require('child_process').spawn,
    ps    = spawn('ps', ['ax']),
    grep  = spawn('grep', ['ssh']);

ps.stdout.on('data', function (data) {
  grep.stdin.write(data);
});

ps.stderr.on('data', function (data) {
  console.log('ps stderr: ' + data);
});

ps.on('close', function (code) {
  if (code !== 0) {
    console.log('ps process exited with code ' + code);
  }
  grep.stdin.end();
});

grep.stdout.on('data', function (data) {
  console.log('' + data);
});

grep.stderr.on('data', function (data) {
  console.log('grep stderr: ' + data);
});

grep.on('close', function (code) {
  if (code !== 0) {
    console.log('grep process exited with code ' + code);
  }
});

Output:

$ node grep.js 6364 pts/0 S+ 0:00 ssh bns-xwing-local

It works!

Running a modified example:

But I notice that grep exits pretty quickly. I want my program to persist, so I'm going to modify the example to leave grep running for a while longer.

var spawn = require('child_process').spawn,
    ps    = spawn('ps', ['ax']),
    grep  = spawn('grep', ['ssh']);

ps.stdout.on('data', function (data) {
  grep.stdin.write(data);
});

ps.stderr.on('data', function (data) {
  console.log('ps stderr: ' + data);
});

ps.on('close', function (code) {
  if (code !== 0) {
    console.log('ps process exited with code ' + code);
  }
  /* ---------------------------------------- */
  //grep.stdin.end(); /* <- Only line changed */
  /* ---------------------------------------- */
});

grep.stdout.on('data', function (data) {
  console.log('' + data);
});

grep.stderr.on('data', function (data) {
  console.log('grep stderr: ' + data);
});

grep.on('close', function (code) {
  if (code !== 0) {
    console.log('grep process exited with code ' + code);
  }
});

Output:

$ node grep.js

No output? Why would that be the case? Closing grep's stdin will cause it to exit, leaving it open keeps me from seeing the output I want. All in all, how is this different from the functionality of .exec?

I tried to link pretty closely to the documentation because I'm confident that Node is working as expected and that I've misread something that brought me here.

Trott commented 9 years ago

STDOUT from grep is being buffered. If you use --line-buffered with grep, things work as you expect:

    grep  = spawn('grep', ['--line-buffered', 'ssh']);
ctag commented 9 years ago

That is the solution for me, thank you!

Since I don't intend to use grep in my end script, I took this and tried to find a more general solution. The unbuffer program, on my distro bundled with expect, appears to do this:

    grep  = spawn('unbuffer', ['grep', 'ssh']);

And for a small set of programs I tried it on, output arrives at each newline.

mykeels commented 2 years ago

For me, I had set an environment variable in the spawn options, like:

spawn(command, {
  env: {
    BROWSER: "none"
  }
}

In Windows, other environment variables seemed to be carried along. But in WSL, they were not, so the program was not working. The solution was to spread the other environment variables such as:

spawn(command, {
  env: {
    ...process.env,
    BROWSER: "none"
  }
}

which worked in both Windows and WSL envs.