Node.js sinh ra quy trình con và đưa đầu ra đầu cuối trực tiếp


113

Tôi có một tập lệnh xuất ra 'hi', ngủ trong một giây, xuất ra 'hi', ngủ trong 1 giây, v.v. Bây giờ tôi nghĩ rằng tôi sẽ có thể giải quyết vấn đề này với mô hình này.

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

temp.stdout.pipe(process.stdout);

Bây giờ vấn đề là nhiệm vụ cần phải được hoàn thành để đầu ra được hiển thị. Như tôi đang hiểu, điều này là do thực tế là quá trình mới được tạo ra có quyền kiểm soát thực thi. Rõ ràng node.js không hỗ trợ các chủ đề nên có giải pháp nào không? Ý tưởng của tôi là có thể chạy hai phiên bản, một phiên bản đầu tiên cho mục đích cụ thể là tạo tác vụ và để nó chuyển đầu ra để xử lý phiên bản thứ hai, coi như điều này có thể đạt được.


1
Nếu quá trình con được viết pythonsau đó đừng quên để vượt qua -ulá cờ cho nó không đệm console đầu ra, nếu không nó sẽ trông giống như kịch bản không phải là sống stackoverflow.com/a/49947671/906265
Aivaras

Sử dụng npmjs.com/package/cross-spawn thay vì bất kỳ thứ gì khác. Nó chỉ tốt hơn.
Andrew Koster

Câu trả lời:


92

Tôi vẫn còn chân ướt chân ráo với Node.js, nhưng tôi có một vài ý tưởng. đầu tiên, tôi tin rằng bạn cần sử dụng execFilethay vì spawn; execFilelà khi bạn có đường dẫn đến một tập lệnh, ngược lại spawnlà để thực hiện một lệnh nổi tiếng mà Node.js có thể phân giải dựa trên đường dẫn hệ thống của bạn.

1. Cung cấp một lệnh gọi lại để xử lý đầu ra được đệm:

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. Thêm trình nghe vào luồng stdout của tiến trình con ( 9port.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()); 
});

Hơn nữa, có vẻ như có các tùy chọn theo đó bạn có thể tách quá trình được tạo ra khỏi thiết bị đầu cuối điều khiển của Node, điều này sẽ cho phép nó chạy không đồng bộ. Tôi chưa thử nghiệm điều này, nhưng có các ví dụ trong tài liệu API giống như sau:

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
Điểm thưởng nếu bạn có thể giải thích cách thực hiện điều này với execute () vì tôi cần thực thi một cmd shell.
DynamicDan

2
Bạn có thể sử dụng child.spawn()với shelltùy chọn được đặt thành true. nodejs.org/api/…
CedX

5
Bạn cũng có thể ống child.stdout trực tiếp đến process.stdout vớichild.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

Nó dễ dàng hơn nhiều bây giờ (6 năm sau)!

Spawn trả về một childObject , sau đó bạn có thể lắng nghe các sự kiện . Các sự kiện là:

  • Lớp: ChildProcess
    • Sự kiện: 'lỗi'
    • Sự kiện: 'thoát'
    • Sự kiện: 'đóng cửa'
    • Sự kiện: 'ngắt kết nối'
    • Sự kiện: 'tin nhắn'

Ngoài ra còn có một loạt các đối tượng từ childObject , chúng là:

  • Lớp: ChildProcess
    • child.stdin
    • child.stdout
    • child.stderr
    • child.stdio
    • child.pid
    • child.connected
    • child.kill ([signal])
    • child.send (message [, sendHandle] [, callback])
    • child.disconnect ()

Xem thêm thông tin tại đây về childObject: https://nodejs.org/api/child_process.html

Không đồng bộ

Nếu bạn muốn chạy quy trình của mình trong nền trong khi nút vẫn có thể tiếp tục thực thi, hãy sử dụng phương thức không đồng bộ. Bạn vẫn có thể chọn thực hiện các hành động sau khi quy trình của bạn hoàn thành và khi quy trình có bất kỳ đầu ra nào (ví dụ: nếu bạn muốn gửi đầu ra của tập lệnh cho máy khách).

child_process.spawn (...); (Nút 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);
});

Đây là cách bạn sử dụng phương thức callback + asynchronous :

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

Sử dụng phương pháp trên, bạn có thể gửi mọi dòng đầu ra từ tập lệnh tới máy khách (ví dụ: sử dụng Socket.io để gửi từng dòng khi bạn nhận các sự kiện trên stdouthoặc stderr).

Đồng bộ

Nếu bạn muốn nút dừng những gì nó đang làm và đợi cho đến khi tập lệnh hoàn tất , bạn có thể sử dụng phiên bản đồng bộ:

child_process.spawnSync (...); (Nút v0.11.12 +)

Các vấn đề với phương pháp này:

  • Nếu tập lệnh mất một lúc để hoàn thành, máy chủ của bạn sẽ bị treo trong khoảng thời gian đó!
  • Stdout sẽ chỉ được trả lại sau khi tập lệnh chạy xong . Bởi vì nó đồng bộ, nó không thể tiếp tục cho đến khi dòng hiện tại kết thúc. Do đó, nó không thể nắm bắt sự kiện 'stdout' cho đến khi dòng sinh sản kết thúc.

Làm thế nào để sử dụng nó:

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, đây sẽ được chọn là câu trả lời đúng ngay bây giờ. Chỉ cần lưu ý, biến dữ liệu trong lệnh gọi lại có dạng đối tượng Buffer. Bạn có thể sử dụng child.stdout.setEncoding('utf8')nếu bạn muốn chuỗi utf8 đến.
Ashish

2
Điều này không hoạt động nếu bạn cần thông tin từ stdoutkhông đồng bộ, nghĩa là, trong khi chương trình còn lại tiếp tục, nếu quá trình tiếp tục.
Christian Hujer

2
Xin chào @ChristianHujer! Tôi đã cập nhật câu trả lời của mình để bao gồm cả không đồng bộ và đồng bộ hóa: D
Katie

nếu bạn có một kịch bản là: console.log("Output 1"); console.error("Boom"); console.log("Output 2");và tôi đang làm spawnAsync('node ./script.js')... làm cách nào để bạn bảo toàn thứ tự của đầu ra? Đầu ra của tôi dường như luôn đi ra không đúng thứ tự.
Bryan Ray

FYI, thậm chí còn dễ dàng hơn nếu bạn sử dụng pipehoặc pipelinehoặc chuyển các tùy chọn thích hợp vào spawn.
RichS

25

Đây là cách tiếp cận sạch nhất mà tôi đã tìm thấy:

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

15
Chính xác thì nó đang làm gì? Tại sao nó hoạt động? Tại sao đây là cách tiếp cận sạch hơn?
raisintering

16

Tôi đã gặp một chút khó khăn khi nhận kết quả ghi nhật ký từ lệnh "npm install" khi tôi tạo npm trong một quy trình con. Ghi nhật ký thời gian thực của các phụ thuộc không hiển thị trong bảng điều khiển mẹ.

Cách đơn giản nhất để thực hiện những gì người đăng ban đầu muốn có vẻ là như thế này (sinh ra npm trên windows và ghi mọi thứ vào bảng điều khiển mẹ):

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

Tôi thấy mình yêu cầu chức năng này thường xuyên đến mức tôi đã đóng gói nó vào một thư viện có tên là std-pour . Nó sẽ cho phép bạn thực hiện một lệnh và xem kết quả đầu ra trong thời gian thực. Để cài đặt đơn giản:

npm install std-pour

Sau đó, nó đủ đơn giản để thực hiện một lệnh và xem kết quả đầu ra trong thời gian thực:

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

Nó được hứa hẹn dựa trên để bạn có thể xâu chuỗi nhiều lệnh. Nó thậm chí còn tương thích với chữ ký chức năng, child_process.spawnvì vậy nó sẽ có thể thay thế ở bất kỳ đâu bạn đang sử dụng.


1
@KodieGrantham rất vui vì nó phù hợp với bạn! Tất cả các bạn có vẻ như bạn đang làm một số công việc thú vị vì vậy tôi hy vọng nó sẽ giúp bạn tiếp tục hoạt động.
Joel B

1

đứa trẻ:

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

cha mẹ:

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

1

Passthru giống 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);
        });
    });
}

Sử dụng

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

0

Thêm câu trả lời liên quan đến child_process.execvì tôi cũng cần phản hồi trực tiếp và không nhận được bất kỳ câu trả lời nào cho đến khi kịch bản hoàn thành. Điều này cũng bổ sung nhận xét của tôi vào câu trả lời được chấp nhận, nhưng vì nó được định dạng nên sẽ dễ hiểu và dễ đọc hơn một chút.

Về cơ bản, tôi có một tập lệnh npm gọi Gulp, gọi một nhiệm vụ mà sau đó sử dụng child_process.exec để thực thi một tập lệnh bash hoặc hàng loạt tùy thuộc vào hệ điều hành. Một trong hai tập lệnh chạy quy trình xây dựng thông qua Gulp và sau đó thực hiện một số lệnh gọi đến một số tệp nhị phân hoạt động với đầu ra Gulp.

Nó giống hệt như những cái khác (đẻ trứng, v.v.), nhưng để hoàn thành, đây chính xác là cách thực hiện:

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

Bây giờ nó đơn giản như thêm một người nghe sự kiện. Đối với stdout:

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

Và cho stderr:

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

Không quá tệ - HTH

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.