node.js使用单独的stdout和stderr流以交互方式生成子进程

gra*_*atz 13 stdout spawn unbuffered node.js

考虑以下C程序(test.c):

#include <stdio.h>

int main() {
  printf("string out 1\n");
  fprintf(stderr, "string err 1\n");
  getchar();
  printf("string out 2\n");
  fprintf(stderr, "string err 2\n");
  fclose(stdout);
}
Run Code Online (Sandbox Code Playgroud)

哪个应该打印一行到stdout,一行到stderr,然后等待用户输入,然后另一行到stdout,另一行到stderr.非常基本!在编译并在命令行上运行时,程序的输出完成(收到getchar()的用户输入):

$ ./test 
string out 1
string err 1

string out 2
string err 2
Run Code Online (Sandbox Code Playgroud)

尝试使用带有以下代码的nodejs将此程序生成为子进程时:

var TEST_EXEC = './test';

var spawn = require('child_process').spawn;
var test = spawn(TEST_EXEC);

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

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

// Simulate entering data for getchar() after 1 second
setTimeout(function() {
  test.stdin.write('\n');
}, 1000);
Run Code Online (Sandbox Code Playgroud)

输出如下所示:

$ nodejs test.js 
stderr: string err 1

stdout: string out 1
string out 2

stderr: string err 2
Run Code Online (Sandbox Code Playgroud)

与在终端中运行./test时看到的输出非常不同.这是因为./test程序在由nodejs生成时没有在交互式shell中运行.test.c stdout流被缓冲,当到达\n时在终端中运行时,刷新缓冲区,但是当以这种方式生成缓冲区时,缓冲区不会被刷新.这可以通过在每次打印后刷新stdout,或者将stdout流更改为无缓冲来解决,以便立即刷新所有内容.假设test.c源不可用或不可修改,则所提到的两个刷新选项都不能实现.

然后我开始考虑模拟交互式shell,pty.js(伪终端)做得很好,例如:

var spawn = require('pty.js').spawn;
var test = spawn(TEST_EXEC);

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

// Simulate entering data for getchar() after 1 second
setTimeout(function() {
  test.write('\n');
}, 1000);
Run Code Online (Sandbox Code Playgroud)

哪个输出:

$ nodejs test.js
data: string out 1
string err 1

data: 

data: string out 2
string err 2
Run Code Online (Sandbox Code Playgroud)

但是stdout和stderr都合并在一起(正如你在终端中运行程序时所看到的那样),我想不出将数据与流分离的方法.

所以问题..

有没有办法使用nodejs来实现输出,就像在运行./test而不修改test.c代码时看到的那样?通过终端仿真或流程产生或任何其他方法?

干杯!

Mat*_*ias 11

我尝试了user568109的答案,但这不起作用,这是有道理的,因为管道只在流之间复制数据.因此,只有在刷新缓冲区时才会进入process.stdout ...以下似乎有效:

var TEST_EXEC = './test';

var spawn = require('child_process').spawn;
var test = spawn(TEST_EXEC, [], { stdio: 'inherit' });

//the following is unfortunately not working 
//test.stdout.on('data', function (data) {
//  console.log('stdout: ' + data);
//});
Run Code Online (Sandbox Code Playgroud)

请注意,这有效地与节点进程共享stdio.不确定你是否可以忍受.

  • 使用带有选项 `{ stdio: 'inherit' }` 的 `spawn()` 确实可以实现对父流的交错输出,但是 - 至少从 `0.10.10` 开始 - **因此你失去了“监听”的能力在通过事件的流上**:从文档中重新了解子进程对象上的`.stdin`、`.stdout`、`.stderr` 属性:“如果子stdio 流与父进程共享,那么这不会设置。” http://nodejs.org/api/child_process.html#child_process_child_stdout。换句话说:从 0.10.10 开始,您的代码在访问 `.stdout` 属性时会失败。如果您解决了这个问题,我很高兴为您的答案 +1。 (4认同)
  • +1用于共享`spawn()`+`{stdio:'inherit'}`技术(即使它没有解决OP的问题).至于听写:有人通过'子类化'`process.stdout.write()`方法找到了*进程本身*的解决方法,但遗憾的是,它不适用于*child*进程写入共享的内容流:https://gist.github.com/bsatrom/1349384 (3认同)

gra*_*atz 5

我只是重新审视这个,因为现在有一个“shell”选项可用于 node 中的 spawn 命令,因为版本 5.7.0。不幸的是,似乎没有生成交互式shell的选项(我也尝试过,shell: '/bin/sh -i'但没有任何乐趣)。但是我刚刚发现建议使用“stdbuf”,允许您更改要运行的程序的缓冲选项。在所有内容上将它们设置为 0 会为所有流产生无缓冲的输出,并且它们仍然保持分开。

这是更新的javascript:

var TEST_EXEC = './test';

var spawn = require('child_process').spawn;
var test = spawn('stdbuf', ['-i0', '-o0', '-e0', TEST_EXEC]);

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

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

// Simulate entering data for getchar() after 1 second
setTimeout(function() {
  test.stdin.write('\n');
}, 1000);
Run Code Online (Sandbox Code Playgroud)

看起来这没有预装在 OSX 上,当然也不适用于 Windows,但可能是类似的替代品。