【问题标题】:Node.js spawn: Keep StdOut and StdErr in the original orderNode.js spawn:保持 StdOut 和 StdErr 的原始顺序
【发布时间】:2019-07-15 20:42:10
【问题描述】:

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

但在测试过程中,stdout 和 stderr 的顺序经常(并非总是如此,但在 80% 的情况下)是混合的。

如何让stdout和stderr保持原来的顺序?


这是我用于测试的 javascript sn-p:

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 重定向到标准输出:

child.stderr.pipe(child.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);

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

【问题讨论】:

    标签: javascript node.js batch-file race-condition spawn


    【解决方案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);
    });
    

    但是,它有一些缺点:

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

    【讨论】:

      【解决方案2】:

      很遗憾,您无法保证订购。

      这些是具有独立缓冲和行为的不同管道。 shell 本身有时会收到超出预期顺序的 STDERR/STDOUT 消息。这也是其他应用程序的典型特征,即使在 Node.js 之外也是如此。

      【讨论】:

      • 是的,我在其他各种应用程序中也遇到过这个问题。尽管如此,它在某些情况下仍能可靠地工作(参见 EDIT 1),但随后捕获输出成为一个问题。
      【解决方案3】:

      如果您不需要区分 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
      

      【讨论】:

      • 谢谢,这行得通,我真的不需要区分消息,但在这个测试中,批处理脚本只是一个例子。我的最终目标是尝试使用 node.js 进行 Windows 自动化(包括启动不同的进程和处理输出)。不幸的是,目前这似乎是个坏主意。
      猜你喜欢
      • 2010-12-05
      • 1970-01-01
      • 1970-01-01
      • 2016-11-19
      • 2018-10-31
      • 1970-01-01
      • 2021-05-26
      • 1970-01-01
      • 2020-05-30
      相关资源
      最近更新 更多