Node.js สร้างกระบวนการย่อยของลูกและรับเอาต์พุตเทอร์มินัลสด


114

ฉันมีสคริปต์ที่ส่งออก '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 ไม่รองรับเธรดดังนั้นโซลูชันใด ๆ ? ความคิดของฉันคืออาจเรียกใช้สองอินสแตนซ์อันแรกเพื่อจุดประสงค์เฉพาะในการสร้างงานและให้มันไปป์ผลลัพธ์ไปยังกระบวนการของอินสแตนซ์ที่สองโดยพิจารณาว่าสามารถทำได้


1
หากมีการเขียนโปรเซสลูกpythonอย่าลืมส่ง-uแฟล็กเพื่อไม่ให้บัฟเฟอร์คอนโซลเอาท์พุทมิฉะนั้นจะดูเหมือนว่าสคริปต์ไม่อยู่ stackoverflow.com/a/49947671/906265
Aivaras

ใช้npmjs.com/package/cross-spawnแทนสิ่งอื่น แค่นั้นดีกว่า
Andrew Koster

คำตอบ:


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

นอกจากนี้ดูเหมือนว่าจะมีตัวเลือกที่คุณสามารถแยกกระบวนการสร้างออกจากเทอร์มินัลควบคุมของโหนดซึ่งจะทำให้สามารถทำงานแบบอะซิงโครนัสได้ ฉันยังไม่ได้ทดสอบ แต่มีตัวอย่างในเอกสาร 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 nodejs.org/api/…true
CedX

5
คุณยังสามารถไพพ์ child.stdout ไปที่ process.stdout ได้โดยตรงด้วยchild.stdout.pipe(process.stdout);
darkadept

@DynamicDanjavascript 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
    • เหตุการณ์: 'ข้อผิดพลาด'
    • เหตุการณ์: 'exit'
    • เหตุการณ์: 'ปิด'
    • เหตุการณ์: 'ยกเลิกการเชื่อมต่อ'
    • เหตุการณ์: 'ข้อความ'

นอกจากนี้ยังมีวัตถุอีกมากมายจาก childObjectได้แก่ :

  • คลาส: ChildProcess
    • child.stdin
    • child.stdout
    • child.stderr
    • child.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 เข้ามา
Ashish

2
วิธีนี้ใช้ไม่ได้หากคุณต้องการข้อมูลจากstdoutแบบอะซิงโครนัสนั่นคือในขณะที่โปรแกรมที่เหลือทำงานต่อไปหากกระบวนการยังคงดำเนินต่อไป
Christian Hujer

2
เฮ้ @ChristianHujer! ฉันอัปเดตคำตอบเพื่อรวมทั้ง async และ sync: D
Katie

ถ้าคุณมีสคริปต์ที่เป็น: console.log("Output 1"); console.error("Boom"); console.log("Output 2");และฉันกำลังทำspawnAsync('node ./script.js')... คุณจะรักษาลำดับของผลลัพธ์ได้อย่างไร? ผลลัพธ์ของฉันดูเหมือนจะออกมาในลำดับที่ไม่ถูกต้องเสมอ
Bryan Ray

FYI ก็จะยิ่งง่ายขึ้นถ้าคุณใช้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-เท ควรให้คุณดำเนินการคำสั่งและดูผลลัพธ์แบบเรียลไทม์ ในการติดตั้งง่ายๆ:

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

passthru ที่เหมือน 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' })

คำตอบนี้ควรมีการโหวตเพิ่มขึ้น
Sahil Ahuja

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 หรือ 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

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.