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

Posted

技术标签:

【中文标题】Node.js 使用单独的 stdout 和 stderr 流以交互方式生成子进程【英文标题】:Node.js spawning a child process interactively with separate stdout and stderr streams 【发布时间】:2013-02-26 16:14:01 【问题描述】:

考虑以下 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);

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

$ ./test 
string out 1
string err 1

string out 2
string err 2

当尝试使用带有以下代码的 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);

输出如下所示:

$ nodejs test.js 
stderr: string err 1

stdout: string out 1
string out 2

stderr: string err 2

与在终端中运行 ./test 时看到的输出非常不同。这是因为 ./test 程序在由 nodejs 生成时没有在交互式 shell 中运行。 test.c 标准输出流被缓冲,当在终端中运行时,一旦到达 \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);

哪些输出:

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

data: 

data: string out 2
string err 2

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

所以问题..

有没有什么方法可以使用 nodejs 来实现运行 ./test 时看到的输出而不修改 test.c 代码?通过终端仿真或进程生成或任何其他方法?

干杯!

【问题讨论】:

感谢您在问题描述中付出了相当大的努力!从节点内运行其他人的 python 脚本时,我遇到了同样的问题,但对底层缓冲机制一无所知。显然(我的 Python 技能 +/- 不存在),只需使用“python -u”即可启用无缓冲 IO 并解决了我的问题! @gratz 你解决了这个问题吗?我面临着类似的问题。你可以从***.com/questions/42130677/… 【参考方案1】:

我尝试了 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);
//);

请注意,这有效地与节点进程共享标准输入输出。不知道你能不能忍受。

【讨论】:

使用 spawn() 和选项 stdio: 'inherit' 确实是启用交错输出到父流的原因,但是 - 至少从 0.10.10 开始 - 您因此失去了“监听”的能力" 在通过事件的流上:来自文档中的 .stdin.stdout.stderr 子进程对象的属性:“如果子 stdio 流与父级共享,那么这不会设置。" nodejs.org/api/child_process.html#child_process_child_stdout.换句话说:从 0.10.10 开始,您的代码在访问 .stdout 属性时会失败。如果您解决了这个问题,我很乐意为您的回答 +1。 你是对的!这很糟糕;我认为像 process.stdout 这样的可写流上没有类似“数据”事件,所以我不知道您是否可以通过某种方式获得该输出:-( +1 用于分享spawn() + stdio: 'inherit' 技术(即使它不能解决 OP 的问题)。至于听写:有人通过“子类化”process.stdout.write() 方法找到了 进程本身 的解决方法,但遗憾的是,它不适用于 child进程写入共享流:gist.github.com/bsatrom/1349384【参考方案2】:

我刚刚重新审视了这一点,因为从 5.7.0 版开始,节点中的 spawn 命令现在有一个“shell”选项可用。不幸的是,似乎没有生成 interactive shell 的选项(我也尝试使用shell: '/bin/sh -i',但没有任何乐趣)。 但是我刚刚发现this 建议使用'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);

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

【讨论】:

那个“stdbuf”技巧解决了我在生成的 shell 中运行的命令的标准输出缓冲问题(在 Raspbian Jessie 上使用 Node v0.10.29)。非常感谢,gratz! 很高兴这有助于@mvanallen【参考方案3】:

你可以这样做:

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

test.stdin.pipe(process.stdin);
test.stdout.pipe(process.stdout);
test.stderr.pipe(process.stderr);

当您使用stdoutstderr 上的事件来打印console.log 上的输出时,由于函数的异步执行,您将得到混乱的输出。输出将为流独立排序,但输出仍可以在 stdinstdoutstderr 之间交错。

【讨论】:

除非我遗漏了什么,否则我如何以这种方式以编程方式使用输出? 您可以将“test”的标准流传输到另一个进程,比如进程“a”(这里是节点本身),其输出将与“test”完全相同,同时仍使用您的代码听测试的输出。 但是要在达到 '\n' 时获得测试的标准输出,该进程需要是终端或伪终端 (pty) 以确保测试管道会像在交互式中运行一样刷新shell .. 其中任何一个都意味着像上面的例子一样失去标准输出和标准错误之间的区别? 这将非常有用:github.com/joyent/node/issues/2754 这就是目标 - 诱使应用程序认为它在交互式终端中运行,只是没有合并的标准输出和标准错误:***.com/questions/1401002/… 你不是说process.stdin.pipe(test.stdin)吗?

以上是关于Node.js 使用单独的 stdout 和 stderr 流以交互方式生成子进程的主要内容,如果未能解决你的问题,请参考以下文章

node js cli STDOUT STDERR 输出 exec 和 spawn 命令

将 Node 的“永久”日志记录到 syslog

如何检测 Node 的 process.stdout 是不是正在通过管道传输?

进程替换 - Node.js child_process

node.js同步执行系统命令

在 node.js 中使用Sharp压缩图像