Node.js создает дочерний процесс и получает вывод терминала в реальном времени

У меня есть сценарий, который выводит «привет», засыпает на секунду, выводит «привет», спит на 1 секунду и т. Д. И т. Д. Теперь я подумал, что смогу решить эту проблему с помощью этой модели.

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

temp.stdout.pipe(process.stdout);

Теперь проблема в том, что задача должна быть завершена, чтобы вывод был отображен. Насколько я понимаю, это связано с тем, что вновь созданный процесс берет на себя управление выполнением. Очевидно, что node.js не поддерживает потоки, поэтому какие-либо решения? Моя идея заключалась в том, чтобы, возможно, запустить два экземпляра, первый для конкретной цели создания задачи и передать вывод в процесс второго экземпляра, учитывая, что это может быть достигнуто.


person foklepoint    schedule 15.01.2013    source источник
comment
Если дочерний процесс записан python, не забудьте передать флаг -u, чтобы он не буферизовал вывод консоли, иначе это будет выглядеть так, как будто сценарий не работает stackoverflow.com/a/49947671/906265   -  person Aivaras    schedule 13.11.2018
comment
Используйте npmjs.com/package/cross-spawn вместо чего-либо еще. Просто лучше.   -  person Andrew Koster    schedule 18.03.2020


Ответы (8)


Я все еще пытаюсь использовать Node.js, но у меня есть несколько идей. во-первых, я считаю, что вам нужно использовать execFile вместо spawn; execFile предназначен для случаев, когда у вас есть путь к сценарию, тогда как spawn предназначен для выполнения хорошо известной команды, которую Node.js может разрешить в соответствии с вашим системным путем.

1. Обеспечьте обратный вызов для обработки буферизованного вывода:

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. Добавьте слушателя в поток дочернего процесса (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 есть примеры нравится:

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()); 
});
person RubyTuesdayDONO    schedule 08.03.2013
comment
Бонусные баллы, если вы можете объяснить, как это сделать с помощью exec (), поскольку мне нужно выполнить командную строку оболочки. - person DynamicDan; 10.04.2014
comment
Вы можете использовать child.spawn() с параметром shell, установленным на true. nodejs.org/api/ - person CedX; 02.06.2016
comment
Вы также можете направить child.stdout напрямую в process.stdout с помощью child.stdout.pipe(process.stdout); - person darkadept; 19.10.2016
comment
@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! } ); - person Rik; 08.06.2018

Сейчас (6 лет спустя) все намного проще!

Spawn возвращает childObject, с помощью которого затем можно прослушивать события. События:

  • Class: ChildProcess
    • Event: 'error'
    • Событие: «выход»
    • Событие: «закрыть»
    • Событие: "отключиться"
    • Событие: "сообщение"

Также есть набор объектов из childObject, это:

  • Class: ChildProcess
    • child.stdin
    • child.stdout
    • child.stderr
    • child.stdio
    • child.pid
    • child.connected
    • child.kill ([сигнал])
    • child.send (сообщение [, sendHandle] [, обратный вызов])
    • 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);
    });
}

Используя описанный выше метод, вы можете отправить клиенту каждую строку вывода сценария (например, используя Socket.io для отправки каждой строки при получении событий на stdout или stderr).

Синхронный

Если вы хотите, чтобы узел остановил свои действия и дождался завершения скрипта, вы можете использовать синхронную версию:

child_process.spawnSync (...); (узел v0.11.12 +)

Проблемы с этим методом:

  • Если сценарий требует времени для завершения, ваш сервер зависнет на это время!
  • Стандартный вывод будет возвращен только после завершения выполнения скрипта. Поскольку он синхронный, он не может продолжаться, пока не закончится текущая строка. Следовательно, он не может захватить событие '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);
person Katie    schedule 30.09.2015
comment
+1, сейчас это следует выбрать как правильный ответ. Просто примечание, переменная данных в обратном вызове входит как объект Buffer. Вы можете использовать child.stdout.setEncoding('utf8'), если хотите, чтобы входили строки utf8. - person Ashish; 17.02.2016
comment
Это не сработает, если вам нужна информация от stdout асинхронно, то есть пока остальная программа продолжается, если процесс продолжается. - person Christian Hujer; 18.11.2018
comment
Привет, @ChristianHujer! Я обновил свой ответ, включив как асинхронность, так и синхронизацию: D - person Katie; 09.08.2019
comment
если у вас есть сценарий: console.log("Output 1"); console.error("Boom"); console.log("Output 2");, а я делаю spawnAsync('node ./script.js') ... как сохранить порядок вывода? Кажется, что мой вывод всегда выходит в неправильном порядке. - person Bryan Ray; 25.04.2020
comment
К вашему сведению, это будет еще проще, если вы используете pipe или pipeline или передадите соответствующие параметры в spawn. - person RichS; 10.05.2020
comment
Если вы используете spawnSync и хотите, чтобы значения возвращались в виде строки, а не буфера, вы захотите передать в {encoding: 'utf-8'} как часть параметров (3-й параметр). - person K. Waite; 18.07.2020
comment
Асинхронное child_process ведение журнала работает, когда задействован только один дочерний процесс. Но когда одновременно запущено несколько дочерних процессов, console.log родительского процесса начинает случайное усечение данных. - person Karl Xu; 12.01.2021

Вот самый чистый подход, который я нашел:

require("child_process").spawn('bash', ['./script.sh'], {
  cwd: process.cwd(),
  detached: true,
  stdio: "inherit"
});
person Harel Ashwal    schedule 16.11.2017
comment
Что именно он делает? Почему это работает? Почему это более чистый подход? - person raisinrising; 16.09.2019

У меня были небольшие проблемы с получением вывода журнала из команды "npm install", когда я создал npm в дочернем процессе. В родительской консоли не отображалось ведение журнала зависимостей в реальном времени.

Самый простой способ сделать то, что хочет исходный плакат, выглядит следующим образом (создать npm в Windows и записать все в родительскую консоль):

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');
});
person agenaille    schedule 05.02.2016

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' })
person Guest    schedule 27.07.2020

ребенок:

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
person Sandro Pasquali    schedule 13.08.2013

Я обнаружил, что мне нужна эта функция достаточно часто, поэтому я упаковал ее в библиотеку под названием 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, поэтому она должна быть заменой везде, где вы Используем это.

person Joel B    schedule 17.06.2017
comment
@KodieGrantham рад, что он работает на вас! Кажется, вы делаете классную работу, так что я надеюсь, что она заставит вас работать. - person Joel B; 25.09.2019

Добавление образца для 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}` ) );

Вы также можете pipe stdout в stdout основного процесса:

childProcess.stdout.pipe( process.stdout );

Совсем неплохо - HTH

person Rik    schedule 08.06.2018