Node.js 生成子进程并实时获取终端输出

Posted

技术标签:

【中文标题】Node.js 生成子进程并实时获取终端输出【英文标题】:Node.js spawn child process and get terminal output live 【发布时间】:2012-12-29 06:08:05 【问题描述】:

我有一个脚本,它输出“hi”,休眠一秒钟,输出“hi”,休眠 1 秒,依此类推。现在我想我可以用这个模型解决这个问题。

var spawn = require('child_process').spawn,
temp    = spawn('PATH TO SCRIPT WITH THE ABOVE BEHAVIOUR');

temp.stdout.pipe(process.stdout);

现在的问题是需要完成任务才能显示输出。据我了解,这是因为新生成的进程具有执行控制权。显然 node.js 不支持线程所以有什么解决方案吗?我的想法是可能运行两个实例,第一个用于创建任务的特定目的,并让它通过管道将输出传递给第二个实例的进程,考虑到这可以实现。

【问题讨论】:

如果子进程写成python,那么不要忘记传递-u 标志以使其不缓冲控制台输出,否则它看起来像脚本不活动***.com/a/49947671/906265 使用npmjs.com/package/cross-spawn 代替其他任何东西。它只是更好。 【参考方案1】:

Asynchronous我喜欢的方法是:

const spawn = require('child_process').spawnSync;
 
try 
    const child = spawn(cmd)
    console.log(child.stdout.toString());
    console.log(child.stderr.toString())
 
 catch (error) 
    console.log(error);
    return error

【讨论】:

【参考方案2】:

我在生成 Python 3 脚本时遇到了上述情况均不起作用的情况。我会从标准输出中获取数据,但只有在孩子终止后。

事实证明,Python 默认缓冲标准输出。可以通过将-u 作为python3 的命令行参数来禁用stdout 缓冲。

【讨论】:

【参考方案3】:

exec 添加一个示例,因为我也需要实时反馈,直到脚本完成后才得到任何反馈。 exec 确实返回一个 EventEmitter,这与许多声称只有 spawn 以这种方式工作的说法相反。

这更彻底地补充了我对已接受答案的评论。

exec的接口类似于spawn:

// INCLUDES
import * as childProcess from 'child_process'; // ES6 Syntax
    
    
// DEFINES
let exec = childProcess.exec; // Use 'var' for more proper 
                              // semantics, or 'const' it all
                              // if that's your thing; though 'let' is 
                              // true-to-scope;

// Return an EventEmitter to work with, though
// you can also chain stdout too: 
// (i.e. exec( ... ).stdout.on( ... ); )
let childProcess = exec
(
    './binary command -- --argument argumentValue',
    ( error, stdout, stderr ) =>
        // When the process completes:
        if( error )
           
            console.log( `$error.name: $error.message` );
            console.log( `[STACK] $error.stack` );
        
            
        console.log( stdout );
        console.log( stderr );
        callback();                // Gulp stuff
    
);

现在就像为stdout注册一个事件处理程序一样简单:

childProcess.stdout.on( 'data', data => console.log( data ) );

对于stderr

childProcess.stderr.on( 'data', data => console.log( `[ERROR]: $data` ) );

你也可以pipestdout 到主进程的stdout:

childProcess.stdout.pipe( process.stdout );

一点也不差——HTH

【讨论】:

【参考方案4】:

我仍然对 Node.js 感兴趣,但我有一些想法。首先,我相信你需要使用execFile 而不是spawnexecFile 用于当您拥有脚本路径时,而spawn 用于执行 Node.js 可以针对您的系统路径解析的众所周知的命令。

1。 Provide a callback 处理缓冲输出:

var child = require('child_process').execFile('path/to/script', [ 
    'arg1', 'arg2', 'arg3', 
], function(err, stdout, stderr)  
    // Node.js will invoke this callback when process terminates.
    console.log(stdout); 
);  

2。为子进程的标准输出添加监听器stream (9thport.net)

var child = require('child_process').execFile('path/to/script', [ 
    'arg1', 'arg2', 'arg3' ]); 
// use event hooks to provide a callback to execute when data are available: 
child.stdout.on('data', function(data) 
    console.log(data.toString()); 
);

此外,似乎有一些选项可以让您将生成的进程与 Node 的控制终端分离,这将允许它异步运行。我还没有对此进行测试,但API docs 中有一些示例如下:

child = require('child_process').execFile('path/to/script', [ 
    'arg1', 'arg2', 'arg3', 
],  
    // detachment and ignored stdin are the key here: 
    detached: true, 
    stdio: [ 'ignore', 1, 2 ]
); 
// and unref() somehow disentangles the child's event loop from the parent's: 
child.unref(); 
child.stdout.on('data', function(data) 
    console.log(data.toString()); 
);

【讨论】:

如果您可以解释如何使用 exec() 执行此操作,则可以加分,因为我需要执行 shell cmd。 您可以将child.spawn()shell 选项设置为true 一起使用。 nodejs.org/api/… 您也可以使用child.stdout.pipe(process.stdout);将 child.stdout 直接通过管道传递给 process.stdout @DynamicDan javascript let childProcess = exec ( './script-to-run --arg1 arg1value', ( error, stdout, stderror ) => console.log( '[CALLBACK]: ' + error ); // or stdout or stderror ); // Same as with spawn: childProcess.stdout.on ( 'data', ( data ) => console.log( '[LIVE]: ' + data ); // Here's your live data! ); 【参考方案5】:

类似 PHP 的通路

import  spawn  from 'child_process';

export default async function passthru(exe, args, options) 
    return new Promise((resolve, reject) => 
        const env = Object.create(process.env);
        const child = spawn(exe, args, 
            ...options,
            env: 
                ...env,
                ...options.env,
            ,
        );
        child.stdout.setEncoding('utf8');
        child.stderr.setEncoding('utf8');
        child.stdout.on('data', data => console.log(data));
        child.stderr.on('data', data => console.log(data));
        child.on('error', error => reject(error));
        child.on('close', exitCode => 
            console.log('Exit code:', exitCode);
            resolve(exitCode);
        );
    );

用法

const exitCode = await passthru('ls', ['-al'],  cwd: '/var/www/html' )

【讨论】:

【参考方案6】:

这是我发现的最干净的方法:

require("child_process").spawn('bash', ['./script.sh'], 
  cwd: process.cwd(),
  detached: true,
  stdio: "inherit"
);

【讨论】:

它到底在做什么?为什么它有效?为什么这是更清洁的方法?【参考方案7】:

现在(6 年后)容易多了!

Spawn 返回一个 childObject,然后您可以使用它监听事件。这些事件是:

类:子进程 事件:“错误” 事件:“退出” 事件:“关闭” 事件:“断开连接” 事件:“消息”

还有一堆来自childObject的对象,它们是:

类:子进程 child.stdin child.stdout child.stderr child.stdio child.pid child.connected child.kill([信号]) child.send(message[, sendHandle][, callback]) child.disconnect()

在此处查看有关 childObject 的更多信息:https://nodejs.org/api/child_process.html

异步

如果您想在节点仍然能够继续执行的情况下在后台运行您的进程,请使用异步方法。您仍然可以选择在流程完成后以及流程有任何输出时执行操作(例如,如果您想将脚本的输出发送到客户端)。

child_process.spawn(...);(节点 v0.1.90)

var spawn = require('child_process').spawn;
var child = spawn('node ./commands/server.js');

// You can also use a variable to save the output 
// for when the script closes later
var scriptOutput = "";

child.stdout.setEncoding('utf8');
child.stdout.on('data', function(data) 
    //Here is where the output goes

    console.log('stdout: ' + data);

    data=data.toString();
    scriptOutput+=data;
);

child.stderr.setEncoding('utf8');
child.stderr.on('data', function(data) 
    //Here is where the error output goes

    console.log('stderr: ' + data);

    data=data.toString();
    scriptOutput+=data;
);

child.on('close', function(code) 
    //Here you can get the exit code of the script

    console.log('closing code: ' + code);

    console.log('Full output of script: ',scriptOutput);
);

下面是如何使用回调 + 异步方法

var child_process = require('child_process');

console.log("Node Version: ", process.version);

run_script("ls", ["-l", "/home"], function(output, exit_code) 
    console.log("Process Finished.");
    console.log('closing code: ' + exit_code);
    console.log('Full output of script: ',output);
);

console.log ("Continuing to do node things while the process runs at the same time...");

// This function will output the lines from the script 
// AS is runs, AND will return the full combined output
// as well as exit code when it's done (using the callback).
function run_script(command, args, callback) 
    console.log("Starting Process.");
    var child = child_process.spawn(command, args);

    var scriptOutput = "";

    child.stdout.setEncoding('utf8');
    child.stdout.on('data', function(data) 
        console.log('stdout: ' + data);

        data=data.toString();
        scriptOutput+=data;
    );

    child.stderr.setEncoding('utf8');
    child.stderr.on('data', function(data) 
        console.log('stderr: ' + data);

        data=data.toString();
        scriptOutput+=data;
    );

    child.on('close', function(code) 
        callback(scriptOutput,code);
    );

使用上述方法,您可以将脚本的每一行输出发送到客户端(例如,当您在stdoutstderr 上接收事件时,使用 Socket.io 发送每一行)。

同步

如果您希望节点停止正在执行的操作并等待脚本完成,您可以使用同步版本:

child_process.spawnSync(...);(节点 v0.11.12+)

这种方法的问题:

如果脚本需要一段时间才能完成,您的服务器将挂起 那段时间! stdout 只会在脚本执行一次后返回 已完成运行。因为是同步的,所以不能继续 直到当前行结束。因此无法捕获 'stdout' 事件,直到生成线完成。

使用方法:

var child_process = require('child_process');

var child = child_process.spawnSync("ls", ["-l", "/home"],  encoding : 'utf8' );
console.log("Process finished.");
if(child.error) 
    console.log("ERROR: ",child.error);

console.log("stdout: ",child.stdout);
console.log("stderr: ",child.stderr);
console.log("exist code: ",child.status);

【讨论】:

+1,现在应该选择这个作为正确答案。请注意,回调中的数据变量以 Buffer 对象的形式出现。如果你想输入 utf8 字符串,你可以使用child.stdout.setEncoding('utf8') 如果你需要来自stdout的信息是异步的,那么这不起作用,也就是说,如果进程继续,则剩余的程序继续。 嘿@ChristianHujer !我更新了我的答案以包括异步和同步:D 如果你有一个脚本是:console.log("Output 1"); console.error("Boom"); console.log("Output 2"); 而我正在做spawnAsync('node ./script.js') ...你如何保持输出的顺序?我的输出似乎总是以错误的顺序输出。 如果您使用 spawnSync 并希望值作为字符串而不是缓冲区返回,您需要输入 encoding: 'utf-8' 作为选项的一部分 (第三个参数)。【参考方案8】:

我发现自己经常需要这个功能,所以我将它打包到一个名为std-pour 的库中。它应该让您执行命令并实时查看输出。简单安装:

npm install std-pour

那么执行命令并实时查看输出就足够简单了:

const  pour  = require('std-pour');
pour('ping', ['8.8.8.8', '-c', '4']).then(code => console.log(`Error Code: $code`));

它是基于承诺的,因此您可以链接多个命令。它甚至与 child_process.spawn 的功能签名兼容,因此它应该是您使用它的任何地方的替代品。

【讨论】:

@KodieGrantham 很高兴它对你有用!你们似乎都在做一些很酷的工作,所以我希望它能让你们继续前进。【参考方案9】:

当我在子进程中生成 npm 时,我在从“npm install”命令获取日志输出时遇到了一点麻烦。依赖项的实时日志记录未显示在父控制台中。

执行原始发布者想要的最简单的方法似乎是这样(在 Windows 上生成 npm 并将所有内容记录到父控制台):

var args = ['install'];

var options = 
    stdio: 'inherit' //feed all child process logging into parent process
;

var childProcess = spawn('npm.cmd', args, options);
childProcess.on('close', function(code) 
    process.stdout.write('"npm install" finished with code ' + code + '\n');
);

【讨论】:

【参考方案10】:

孩子:

setInterval(function() 
    process.stdout.write("hi");
, 1000); // or however else you want to run a timer

父母:

require('child_process').fork('./childfile.js');
// fork'd children use the parent's stdio

【讨论】:

以上是关于Node.js 生成子进程并实时获取终端输出的主要内容,如果未能解决你的问题,请参考以下文章

golang如何获取子进程的实时输出

使用子进程获取实时输出

Python子进程显示在终端上登录并保存在文件中

获取 node.js 中所有嵌套子进程的标准输出

从分离的子进程获取输出

如何在 node.js 子进程模块中将消息和标准输出从子进程传递给父进程?