繁体   English   中英

Node.js spawn:保持 StdOut 和 StdErr 的原始顺序

[英]Node.js spawn: Keep StdOut and StdErr in the original order

尝试从 node.js v12.6.0 运行 Windows 批处理脚本并以正确的顺序实时捕获其输出。

但是 stdout 和 stderr 的顺序在测试期间是混合的(并非总是如此,但在 80% 的情况下)。

如何保持标准输出和标准错误的原始顺序?


这是我用于测试的 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 本身似乎“知道”如何正确执行此操作。

但在这种情况下,我不知道如何捕获输出:

child.stdout

为“null”,并尝试侦听 node.js 本身的处理标准输出:

process.stdout.on('data' ...)

在任何配置中都会给出“错误:读取 ENOTCONN”。


编辑2:

如果合并流并以这种方式收听:

const mergedStream = child.stdout.wrap(child.stderr);

mergedStream.on('data' ...);

我们有一个 stdout 和 stderr 的侦听器,但排序仍然无效。


编辑 3:

如果您想捕获输出显示它,是否可以保持正确的顺序。

要显示正确的输出而不捕获它,请参阅“编辑 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);

为了避免在“数据”回调中引用“进程”,排序也会中断。

在不修改可执行文件的情况下,可接受的方法是(注意 spawn 选项和命令参数):

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

但是,它有一些缺点:

  1. 它产生额外的控制台流程实例。
  2. 如果您想使用 'windowsHide: true' spawn options 参数来隐藏带有 GUI 的应用程序,请避免使用 'shell: true' 参数启动进程,因为只会隐藏控制台窗口,而不是目标应用程序窗口。
  3. 您无法区分 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.
    }
});

不幸的是,您不能保证订购。

这些是具有独立缓冲和行为的不同管道。 外壳本身有时会以不符合预期顺序的方式获取 STDERR/STDOUT 消息。 这也是其他应用程序的典型特征,甚至在 Node.js 之外也是如此。

如果不需要区分 STDOUT 和 STDERR,可以在源头(批处理文件内)加入它们。 然后批处理文件将所有 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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM