Node.js spawn:保持 StdOut 和 StdErr 的原始顺序
Node.js spawn: Keep StdOut and StdErr in the original order
正在尝试 运行 windows 来自 node.js v12.6.0 的批处理脚本,并以正确的顺序实时捕获其输出。
但是在测试期间,stdout 和 stderr 的顺序经常(不总是,但在 80% 的情况下)混合。
如何保持 stdout 和 stderr 的原始顺序?
这是我用于测试的 javascript 片段:
const spawn = require('child_process').spawn;
// Using 'inherit' to fix:
// "ERROR: Input redirection is not supported, exiting the process immediately".
const options = {
stdio: [
'inherit', // StdIn.
'pipe', // StdOut.
'pipe' // StdErr.
],
};
const child = spawn('exectest.cmd', options);
let mergedOut = '';
child.stdout.setEncoding('utf8');
child.stdout.on('data', (chunk) => {
process.stdout.write(chunk);
mergedOut += chunk;
});
child.stderr.setEncoding('utf8');
child.stderr.on('data', (chunk) => {
process.stderr.write(chunk);
mergedOut += chunk;
});
child.on('close', (code, signal) => {
console.log('-'.repeat(30));
console.log(mergedOut);
});
我尝试将 stderr 重定向到 stdout:
child.stderr.pipe(child.stdout);
并删除 stderr 侦听器(仅处理 stdout):
child.stderr.on
为了避免竞争条件,但 stderr 没有显示在控制台中,也没有被添加到 'mergedOut'。
这是我尝试 运行 (exectest.cmd):
的批处理文件的内容
@Echo Off
ChCp 65001 >Nul
Echo 1 (stdout) ...
Echo 2 (stderr) ... 1>&2
Echo 3 (stderr) ... 1>&2
Echo 4 (stdout) ...
Echo 5 (stdout) ...
Echo 6 (stderr) ... 1>&2
当前输出:
1 (stdout) ...
2 (stderr) ...
3 (stderr) ...
6 (stderr) ...
4 (stdout) ...
5 (stdout) ...
------------------------------
1 (stdout) ...
2 (stderr) ...
3 (stderr) ...
6 (stderr) ...
4 (stdout) ...
5 (stdout) ...
预期输出:
1 (stdout) ...
2 (stderr) ...
3 (stderr) ...
4 (stdout) ...
5 (stdout) ...
6 (stderr) ...
------------------------------
1 (stdout) ...
2 (stderr) ...
3 (stderr) ...
4 (stdout) ...
5 (stdout) ...
6 (stderr) ...
编辑 1:
有:
stdio: [
'inherit', // StdIn.
'inherit', // StdOut.
'inherit' // StdErr.
],
输出顺序可靠地正确,所以 node.js 本身似乎 'know'
如何正确执行此操作。
但在这种情况下,我不知道如何捕获输出:
child.stdout
是 'null',并尝试监听 node.js 本身的进程标准输出:
process.stdout.on('data' ...)
在任何配置中给出 'Error: read ENOTCONN'。
编辑 2:
如果合并流并以这种方式收听:
const mergedStream = child.stdout.wrap(child.stderr);
mergedStream.on('data' ...);
我们有一个 stdout 和 stderr 的监听器,但顺序仍然不正确。
编辑 3:
如果您想捕获输出,可以保持正确的顺序或显示它。
要在不捕获输出的情况下显示正确的输出,请参阅 'EDIT 1'。
要捕获正确的输出而不实时显示,只需使用:
child.stdout.on('data', (chunk) => {
mergedOut += chunk;
});
child.stderr.on('data', (chunk) => {
mergedOut += chunk;
});
但是一旦您尝试在以下内容中引用 'process.stdout / process.stderr':
child.stdout.on('data' ...)
回调,排序会中断
例如:
child.stdout.on('data', (chunk) => {
process.stdout; // <-- This line will break everything.
// You do not even need to .write() to it!
});
或者如果您尝试:
child.stdout.pipe(process.stdout);
child.stderr.pipe(process.stderr);
为了避免在 'data' 回调中引用 'process',
排序也会中断。
很遗憾,您不能保证一定会订购。
这些是具有独立缓冲和行为的不同管道。 shell 本身有时会按预期顺序获取 STDERR/STDOUT 消息。这也是其他应用程序的典型特征,甚至在 Node.js.
之外
如果不需要区分STDOUT和STDERR,可以在源头(批处理文件内)加入。然后批处理文件 returns 所有 STDERR 和 STDOUT 仅作为 STDOUT
@Echo Off
ChCp 65001 >Nul
(
Echo 1 [stdout] ...
Echo 2 [stderr] ... 1>&2
Echo 3 [stderr] ... 1>&2
Echo 4 [stdout] ...
Echo 5 [stdout] ...
Echo 6 [stderr] ... 1>&2
) 2>&1
在不修改可执行文件的情况下,可接受的方法是(注意生成选项和命令参数):
const { spawn } = require('child_process');
const options = {
shell: true,
stdio: [
'inherit', // StdIn.
'pipe', // StdOut.
'pipe', // StdErr.
],
};
const child = spawn('exectest.cmd', ['2>&1'], options);
let mergedOut = '';
child.stdout.setEncoding('utf8');
child.stdout.on('data', (chunk) => {
process.stdout.write(chunk, (_err) => { });
mergedOut += chunk;
});
child.on('close', (_code, _signal) => {
console.log('-'.repeat(30));
console.log(mergedOut);
});
但是,它也有一些缺点:
- 它生成额外的控制台进程实例。
- 如果您想使用 'windowsHide: true' spawn options 参数来隐藏带有 GUI 的应用程序,请避免使用 'shell: true' 参数启动进程,因为只会隐藏控制台 window,而不是目标申请 window.
- 您无法区分StdOut 和StdErr。根据目标应用程序的输出格式,此问题的解决方案可能如下所示:
const readline = require('readline');
// ...
const lineReader = readline.createInterface({
input: child.stdout
});
lineReader.on('line', (line) => {
if (line.includes('[ERROR]:')) {
console.error(line); // StdErr.
} else {
console.log(line); // StdOut.
}
});
正在尝试 运行 windows 来自 node.js v12.6.0 的批处理脚本,并以正确的顺序实时捕获其输出。
但是在测试期间,stdout 和 stderr 的顺序经常(不总是,但在 80% 的情况下)混合。
如何保持 stdout 和 stderr 的原始顺序?
这是我用于测试的 javascript 片段:
const spawn = require('child_process').spawn;
// Using 'inherit' to fix:
// "ERROR: Input redirection is not supported, exiting the process immediately".
const options = {
stdio: [
'inherit', // StdIn.
'pipe', // StdOut.
'pipe' // StdErr.
],
};
const child = spawn('exectest.cmd', options);
let mergedOut = '';
child.stdout.setEncoding('utf8');
child.stdout.on('data', (chunk) => {
process.stdout.write(chunk);
mergedOut += chunk;
});
child.stderr.setEncoding('utf8');
child.stderr.on('data', (chunk) => {
process.stderr.write(chunk);
mergedOut += chunk;
});
child.on('close', (code, signal) => {
console.log('-'.repeat(30));
console.log(mergedOut);
});
我尝试将 stderr 重定向到 stdout:
child.stderr.pipe(child.stdout);
并删除 stderr 侦听器(仅处理 stdout):
child.stderr.on
为了避免竞争条件,但 stderr 没有显示在控制台中,也没有被添加到 'mergedOut'。
这是我尝试 运行 (exectest.cmd):
的批处理文件的内容@Echo Off
ChCp 65001 >Nul
Echo 1 (stdout) ...
Echo 2 (stderr) ... 1>&2
Echo 3 (stderr) ... 1>&2
Echo 4 (stdout) ...
Echo 5 (stdout) ...
Echo 6 (stderr) ... 1>&2
当前输出:
1 (stdout) ...
2 (stderr) ...
3 (stderr) ...
6 (stderr) ...
4 (stdout) ...
5 (stdout) ...
------------------------------
1 (stdout) ...
2 (stderr) ...
3 (stderr) ...
6 (stderr) ...
4 (stdout) ...
5 (stdout) ...
预期输出:
1 (stdout) ...
2 (stderr) ...
3 (stderr) ...
4 (stdout) ...
5 (stdout) ...
6 (stderr) ...
------------------------------
1 (stdout) ...
2 (stderr) ...
3 (stderr) ...
4 (stdout) ...
5 (stdout) ...
6 (stderr) ...
编辑 1:
有:
stdio: [
'inherit', // StdIn.
'inherit', // StdOut.
'inherit' // StdErr.
],
输出顺序可靠地正确,所以 node.js 本身似乎 'know' 如何正确执行此操作。
但在这种情况下,我不知道如何捕获输出:
child.stdout
是 'null',并尝试监听 node.js 本身的进程标准输出:
process.stdout.on('data' ...)
在任何配置中给出 'Error: read ENOTCONN'。
编辑 2:
如果合并流并以这种方式收听:
const mergedStream = child.stdout.wrap(child.stderr);
mergedStream.on('data' ...);
我们有一个 stdout 和 stderr 的监听器,但顺序仍然不正确。
编辑 3:
如果您想捕获输出,可以保持正确的顺序或显示它。
要在不捕获输出的情况下显示正确的输出,请参阅 'EDIT 1'。
要捕获正确的输出而不实时显示,只需使用:
child.stdout.on('data', (chunk) => {
mergedOut += chunk;
});
child.stderr.on('data', (chunk) => {
mergedOut += chunk;
});
但是一旦您尝试在以下内容中引用 'process.stdout / process.stderr':
child.stdout.on('data' ...)
回调,排序会中断
例如:
child.stdout.on('data', (chunk) => {
process.stdout; // <-- This line will break everything.
// You do not even need to .write() to it!
});
或者如果您尝试:
child.stdout.pipe(process.stdout);
child.stderr.pipe(process.stderr);
为了避免在 'data' 回调中引用 'process', 排序也会中断。
很遗憾,您不能保证一定会订购。
这些是具有独立缓冲和行为的不同管道。 shell 本身有时会按预期顺序获取 STDERR/STDOUT 消息。这也是其他应用程序的典型特征,甚至在 Node.js.
之外如果不需要区分STDOUT和STDERR,可以在源头(批处理文件内)加入。然后批处理文件 returns 所有 STDERR 和 STDOUT 仅作为 STDOUT
@Echo Off
ChCp 65001 >Nul
(
Echo 1 [stdout] ...
Echo 2 [stderr] ... 1>&2
Echo 3 [stderr] ... 1>&2
Echo 4 [stdout] ...
Echo 5 [stdout] ...
Echo 6 [stderr] ... 1>&2
) 2>&1
在不修改可执行文件的情况下,可接受的方法是(注意生成选项和命令参数):
const { spawn } = require('child_process');
const options = {
shell: true,
stdio: [
'inherit', // StdIn.
'pipe', // StdOut.
'pipe', // StdErr.
],
};
const child = spawn('exectest.cmd', ['2>&1'], options);
let mergedOut = '';
child.stdout.setEncoding('utf8');
child.stdout.on('data', (chunk) => {
process.stdout.write(chunk, (_err) => { });
mergedOut += chunk;
});
child.on('close', (_code, _signal) => {
console.log('-'.repeat(30));
console.log(mergedOut);
});
但是,它也有一些缺点:
- 它生成额外的控制台进程实例。
- 如果您想使用 'windowsHide: true' spawn options 参数来隐藏带有 GUI 的应用程序,请避免使用 'shell: true' 参数启动进程,因为只会隐藏控制台 window,而不是目标申请 window.
- 您无法区分StdOut 和StdErr。根据目标应用程序的输出格式,此问题的解决方案可能如下所示:
const readline = require('readline');
// ...
const lineReader = readline.createInterface({
input: child.stdout
});
lineReader.on('line', (line) => {
if (line.includes('[ERROR]:')) {
console.error(line); // StdErr.
} else {
console.log(line); // StdOut.
}
});