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


114

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

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

temp.stdout.pipe(process.stdout);

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


1
Если дочерний процесс записан python, не забудьте передать -uфлаг, чтобы он не буферизовал вывод консоли, иначе он будет выглядеть так, как будто скрипт не работает stackoverflow.com/a/49947671/906265
Айварас

Используйте npmjs.com/package/cross-spawn вместо чего-либо еще. Просто лучше.
Эндрю Костер,

Ответы:


95

Я все еще пытаюсь освоить 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. Добавьте слушателя в поток stdout дочернего процесса ( 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()); 
});

8
Бонусные баллы, если вы можете объяснить, как это сделать с помощью exec (), поскольку мне нужно выполнить команду оболочки.
DynamicDan

2
Вы можете использовать, child.spawn()если для shellпараметра установлено значение true. nodejs.org/api/…
CedX 02

5
Вы также можете передать child.stdout напрямую в process.stdout с помощьюchild.stdout.pipe(process.stdout);
darkadept

@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! } );
Rik

131

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

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

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

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

  • Класс: 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).

Синхронный

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

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

11
+1, сейчас это следует выбрать как правильный ответ. Просто примечание, переменная данных в обратном вызове входит как объект Buffer. Вы можете использовать, child.stdout.setEncoding('utf8')если хотите, чтобы вводились строки utf8.
Ашиш

2
Это не сработает, если вам нужна информация из stdoutасинхронного режима, то есть пока остающаяся программа продолжает работу, если процесс продолжается.
Christian Hujer

2
Привет, @ChristianHujer! Я обновил свой ответ, включив в него асинхронность и синхронизацию: D
Кэти,

если у вас есть сценарий: console.log("Output 1"); console.error("Boom"); console.log("Output 2");а я делаю spawnAsync('node ./script.js')... как сохранить порядок вывода? Кажется, что мой вывод всегда выходит в неправильном порядке.
Брайан Рэй

К вашему сведению, это еще проще, если вы используете pipeили pipelineили передадите соответствующие параметры в spawn.
RichS

25

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

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

15
Что именно он делает? Почему это работает? Почему это более чистый подход?
raisinrising

16

У меня были небольшие проблемы с получением вывода журнала из команды "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');
});

3

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


1
@KodieGrantham рад, что он работает на вас! Кажется, вы делаете классную работу, так что я надеюсь, что она заставит вас работать.
Joel B

2

PHP-подобный passthru

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

Этот ответ должен иметь больше голосов
Сахил Ахуджа

1

ребенок:

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

0

Добавление ответа, связанного с тем, child_process.execчто мне тоже нужна была обратная связь в реальном времени, и я не получал ее до завершения сценария. Это также дополняет мой комментарий к принятому ответу, но по мере его форматирования он будет немного более понятным и легким для чтения.

По сути, у меня есть сценарий npm, который вызывает Gulp, вызывая задачу, которая впоследствии используется child_process.execдля выполнения сценария bash или пакетной обработки в зависимости от ОС. Любой сценарий запускает процесс сборки через Gulp, а затем выполняет несколько вызовов некоторых двоичных файлов, которые работают с выходными данными Gulp.

Это точно так же, как и другие (спавн и т. Д.), Но для завершения вот как это сделать:

// INCLUDES
import * as childProcess from 'child_process'; // ES6 Syntax


// GLOBALS
let exec = childProcess.exec; // Or use 'var' for more proper 
                              // semantics, though 'let' is 
                              // true-to-scope


// Assign exec to a variable, or chain stdout at the end of the call
// to exec - the choice, yours (i.e. exec( ... ).stdout.on( ... ); )
let childProcess = exec
(
    './binary command -- --argument argumentValue',
    ( error, stdout, stderr ) =>
    {
        if( error )
        {
            // This won't show up until the process completes:
            console.log( '[ERROR]: "' + error.name + '" - ' + error.message );
            console.log( '[STACK]: ' + error.stack );

            console.log( stdout );
            console.log( stderr );
            callback();            // Gulp stuff
            return;
        }

        // Neither will this:
        console.log( stdout );
        console.log( stderr );
        callback();                // Gulp stuff
    }
);

Теперь это так же просто, как добавить прослушиватель событий. Для stdout:

childProcess.stdout.on
(
    'data',
    ( data ) =>
    {
        // This will render 'live':
        console.log( '[STDOUT]: ' + data );
    }
);

И для stderr:

childProcess.stderr.on
(
    'data',
    ( data ) =>
    {
        // This will render 'live' too:
        console.log( '[STDERR]: ' + data );
    }
);

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.