Node.js породжує дочірній процес та отримує вихідний термінал


113

У мене є сценарій, який видає "привіт", спить секунду, виводить "привіт", спить 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 замість чого-небудь іншого. Це просто краще.
Андрій Костер

Відповіді:


92

Я все ще мочу ногами від 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 (), оскільки мені потрібно виконати cmd оболонки.
DynamicDan

2
Ви можете використовувати child.spawn()з shellопцією, встановленою на true. nodejs.org/api/…
CedX

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

130

Зараз набагато простіше (через 6 років)!

Spawn повертає дочірній об’єкт , з яким потім можна слухати події . Події:

  • Клас: ChildProcess
    • Подія: "помилка"
    • Подія: "вихід"
    • Подія: "закрити"
    • Подія: "відключити"
    • Подія: 'повідомлення'

Також є купа об’єктів від childObject , це:

  • Клас: ChildProcess
    • дитина.стдін
    • child.stdout
    • child.stderr
    • дитина.stdio
    • child.pid
    • дитина.пов’язаний
    • 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 буде повернута лише після завершення запуску сценарію . Оскільки це синхронно, він не може продовжуватись, поки поточний рядок не закінчиться. Тому він не в змозі зафіксувати подію "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, зараз це слід вибрати як правильну відповідь. Просто зауважте, що змінна даних у зворотному дзвінку надходить як буферний об'єкт. Ви можете використовувати, child.stdout.setEncoding('utf8')якщо хочете, щоб увійшли рядки utf8.
Ашиш

2
Це не працює, якщо вам потрібна інформація з stdoutасинхронного зв’язку, тобто, поки програма, що залишилася, продовжується, якщо процес триває.
Крістіан Худжер

2
Привіт @ChristianHujer! Я оновив свою відповідь, щоб включити як асинхронізацію, так і синхронізацію: D
Katie

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

FYI, це навіть легше , якщо ви використовуєте pipeабо pipelineабо передати в відповідних опціях spawn.
RichS

25

Ось найчистіший підхід, який я знайшов:

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

15
Що саме це робить? Чому це працює? Чому це чистіший підхід?
родзинкування

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 радий, що він працює на тебе! Вам здається, ви робите якісь круті роботи, тож сподіваюся, що це продовжує працювати.
Джоель Б

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

1

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

0

Додавання відповіді, пов’язаної з тим, child_process.execяк мені теж потрібні були відгуки в реальному часі, і я не отримував їх до завершення сценарію. Це також доповнює мій коментар до прийнятої відповіді, але у форматі він буде трохи зрозумілішим та легшим для читання.

В основному у мене є сценарій npm, який викликає Gulp, викликаючи завдання, яке згодом використовується child_process.execдля виконання скрипта bash або batch в залежності від ОС. Будь-який сценарій запускає процес збирання через 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.