Node.js gera o processo filho e obtém a saída do terminal ao vivo

113

Tenho um script que emite 'oi', dorme por um segundo, produz 'oi', dorme por 1 segundo e assim por diante. Agora achei que seria capaz de resolver esse problema com este modelo.

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

temp.stdout.pipe(process.stdout);

Agora, o problema é que a tarefa precisa ser concluída para que a saída seja exibida. Pelo que entendi, isso se deve ao fato de que o processo recém-gerado assume o controle de execução. Obviamente, node.js não oferece suporte a threads, então alguma solução? Minha ideia era possivelmente executar duas instâncias, a primeira com o propósito específico de criar a tarefa e fazer com que ela canalizasse a saída para o processo da segunda instância, considerando que isso pode ser alcançado.

foklepoint
fonte
1
Se o processo filho for gravado python, não se esqueça de passar o -usinalizador para que ele não faça buffer da saída do console, caso contrário, parecerá que o script não está
ativo
Use npmjs.com/package/cross-spawn em vez de qualquer outra coisa. É apenas melhor.
Andrew Koster,

Respostas:

92

Ainda estou começando a usar o Node.js, mas tenho algumas ideias. primeiro, acredito que você precisa usar em execFilevez de spawn; execFileé para quando você tem o caminho para um script, enquanto spawné para executar um comando conhecido que o Node.js pode resolver em relação ao caminho do sistema.

1. Forneça um retorno de chamada para processar a saída em buffer:

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. Adicione um ouvinte ao fluxo stdout do processo filho ( 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()); 
});

Além disso, parece haver opções pelas quais você pode desanexar o processo gerado do terminal de controle do Node, o que permitiria que ele fosse executado de forma assíncrona. Eu não testei isso ainda, mas existem exemplos nos documentos da API que são mais ou menos assim:

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()); 
});
RubyT TuesdayDONO
fonte
8
Pontos de bônus se você puder explicar como fazer isso com exec (), pois preciso executar um cmd shell.
DynamicDan de
2
Você pode usar child.spawn()com a shellopção definida como true. nodejs.org/api/…
CedX
5
Você também pode enviar child.stdout diretamente para process.stdout comchild.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

É muito mais fácil agora (6 anos depois)!

Spawn retorna um childObject , com o qual você pode escutar eventos . Os eventos são:

  • Classe: ChildProcess
    • Evento: 'erro'
    • Evento: 'saída'
    • Evento: 'fechar'
    • Evento: 'desconectar'
    • Evento: 'mensagem'

Existem também vários objetos de childObject , são eles:

  • Classe: ChildProcess
    • child.stdin
    • child.stdout
    • child.stderr
    • child.stdio
    • child.pid
    • child.connected
    • child.kill ([sinal])
    • child.send (mensagem [, sendHandle] [, callback])
    • child.disconnect ()

Veja mais informações aqui sobre childObject: https://nodejs.org/api/child_process.html

Assíncrono

Se você deseja executar seu processo em segundo plano enquanto o nó ainda é capaz de continuar a executar, use o método assíncrono. Você ainda pode optar por executar ações após a conclusão do processo e quando o processo tiver alguma saída (por exemplo, se você quiser enviar a saída de um script para o cliente).

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

Veja como você usaria um retorno de chamada + método assíncrono :

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

Usando o método acima, você pode enviar todas as linhas de saída do script para o cliente (por exemplo, usando Socket.io para enviar cada linha quando receber eventos em stdoutou stderr).

Síncrono

Se você quiser que o nó pare o que está fazendo e espere até que o script seja concluído , você pode usar a versão síncrona:

child_process.spawnSync (...); (Nó v0.11.12 +)

Problemas com este método:

  • Se o script demorar um pouco para ser concluído, seu servidor irá travar por esse tempo!
  • O stdout só será retornado quando o script terminar de ser executado . Por ser síncrono, não pode continuar até que a linha atual termine. Portanto, ele é incapaz de capturar o evento 'stdout' até que a linha de spawn termine.

Como usá-lo:

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);
Katie
fonte
11
+1, esta deve ser escolhida como a resposta certa agora. Apenas uma observação: a variável de dados no retorno de chamada vem como objeto Buffer. Você pode usar child.stdout.setEncoding('utf8')se quiser cordas utf8 chegando.
Ashish
2
Isso não funciona se você precisar das informações de stdoutforma assíncrona, ou seja, enquanto o programa restante continua, se o processo continua.
Christian Hujer
2
Olá, @ChristianHujer! Atualizei minha resposta para incluir assíncrono e sincronizado: D
Katie
se você tem um script que é: console.log("Output 1"); console.error("Boom"); console.log("Output 2");e eu estou fazendo spawnAsync('node ./script.js')... como você preserva a ordem da saída? Minha saída sempre parece sair na ordem incorreta.
Bryan Ray
Para sua informação, é ainda mais fácil se você usar pipeou pipelineou passar as opções apropriadas para spawn.
RichS
25

Aqui está a abordagem mais limpa que encontrei:

require("child_process").spawn('bash', ['./script.sh'], {
  cwd: process.cwd(),
  detached: true,
  stdio: "inherit"
});
Harel Ashwal
fonte
15
O que está fazendo exatamente? Por que isso funciona? Por que essa é a abordagem mais limpa?
aumentando em
16

Tive alguns problemas para obter a saída de registro do comando "npm install" quando gerei o npm em um processo filho. O registro em tempo real de dependências não foi mostrado no console pai.

A maneira mais simples de fazer o que o autor original deseja parece ser esta (gerar o npm no Windows e registrar tudo no console dos pais):

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');
});
agenaille
fonte
3

Eu me vi exigindo essa funcionalidade com freqüência suficiente para colocá-la em uma biblioteca chamada std-pour . Deve permitir que você execute um comando e visualize a saída em tempo real. Para instalar simplesmente:

npm install std-pour

Então é simples executar um comando e ver a saída em tempo real:

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

É prometido com base para que você possa encadear vários comandos. É até compatível com a assinatura de funções, child_process.spawnportanto, deve ser uma substituição em qualquer lugar que você esteja usando.

Joel B
fonte
1
@KodieGrantham que bom que está funcionando para você! Parece que vocês estão fazendo um trabalho legal, então espero que isso os mantenha funcionando.
Joel B
1

criança:

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

pai:

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

Passthru semelhante a 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);
        });
    });
}

Uso

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

Adicionando uma resposta relacionada a child_process.execcomo eu também precisava de feedback ao vivo e não receberia até depois que o script fosse concluído. Isso também complementa meu comentário para a resposta aceita, mas, à medida que for formatado, será um pouco mais compreensível e fácil de ler.

Basicamente, eu tenho um script npm que chama Gulp, invocando uma tarefa que posteriormente usa child_process.execpara executar um script bash ou batch dependendo do sistema operacional. Qualquer um dos scripts executa um processo de construção via Gulp e, em seguida, faz algumas chamadas para alguns binários que funcionam com a saída do Gulp.

É exatamente como os outros (spawn, etc.), mas para fins de conclusão, veja exatamente como fazer:

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

Agora é tão simples quanto adicionar um ouvinte de evento. Para stdout:

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

E para stderr:

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

Nada mal - HTH

Rik
fonte