Execute um binário de linha de comando com o Node.js

Eu estou no processo de portar uma biblioteca CLI do Ruby para o Node.js. No meu código eu executo vários binários de terceiros quando necessário. Não tenho certeza de como fazer isso melhor no Node.

Aqui está um exemplo em Ruby onde eu chamo PrinceXML para converter um arquivo em PDF:

cmd = system("prince -v builds/pdf/book.html -o builds/pdf/book.pdf") 

Qual é o código equivalente no Node?

Para uma versão ainda mais recente do Node.js (v8.1.4), os events e as chamadas são semelhantes ou idênticos às versões mais antigas, mas é recomendável usar os resources de idioma mais recentes padrão. Exemplos:

Para saída formatada em buffer e não em stream (você obtém tudo de uma vez), use child_process.exec :

 const { exec } = require('child_process'); exec('cat *.js bad_file | wc -l', (err, stdout, stderr) => { if (err) { // node couldn't execute the command return; } // the *entire* stdout and stderr (buffered) console.log(`stdout: ${stdout}`); console.log(`stderr: ${stderr}`); }); 

Você também pode usá-lo com promises:

 const util = require('util'); const exec = util.promisify(require('child_process').exec); async function ls() { const { stdout, stderr } = await exec('ls'); console.log('stdout:', stdout); console.log('stderr:', stderr); } ls(); 

Se você deseja receber os dados gradualmente em partes (saída como um stream), use child_process.spawn :

 const { spawn } = require('child_process'); const child = spawn('ls', ['-lh', '/usr']); // use child.stdout.setEncoding('utf8'); if you want text chunks child.stdout.on('data', (chunk) => { // data from standard output is here as buffers }); // since these are streams, you can pipe them elsewhere child.stderr.pipe(dest); child.on('close', (code) => { console.log(`child process exited with code ${code}`); }); 

Ambas estas funções têm uma contrapartida síncrona. Um exemplo para child_process.execSync :

 const { execSync } = require('child_process'); // stderr is sent to stdout of parent process // you can set options.stdio if you want it to go elsewhere let stdout = execSync('ls'); 

Bem como child_process.spawnSync :

 const { spawnSync} = require('child_process'); const child = spawnSync('ls', ['-lh', '/usr']); console.log('error', child.error); console.log('stdout ', child.stdout); console.log('stderr ', child.stderr); 

Nota: O código a seguir ainda está funcional, mas é voltado principalmente para usuários do ES5 e anteriores.

O módulo para geração de processos filhos com o Node.js está bem documentado na documentação (v5.0.0). Para executar um comando e buscar sua saída completa como um buffer, use child_process.exec :

 var exec = require('child_process').exec; var cmd = 'prince -v builds/pdf/book.html -o builds/pdf/book.pdf'; exec(cmd, function(error, stdout, stderr) { // command output is in stdout }); 

Se você precisar usar o manipulador de E / S do processo com streams, por exemplo, quando estiver esperando grandes quantidades de saída, use child_process.spawn :

 var spawn = require('child_process').spawn; var child = spawn('prince', [ '-v', 'builds/pdf/book.html', '-o', 'builds/pdf/book.pdf' ]); child.stdout.on('data', function(chunk) { // output will be here in chunks }); // or if you want to send output elsewhere child.stdout.pipe(dest); 

Se você estiver executando um arquivo em vez de um comando, poderá usar child_process.execFile , parâmetros que são quase idênticos ao spawn , mas tem um quarto parâmetro de retorno de chamada, como o exec para recuperar os buffers de saída. Isso pode parecer um pouco assim:

 var execFile = require('child_process').execFile; execFile(file, args, options, function(error, stdout, stderr) { // command output is in stdout }); 

A partir da v0.11.12 , o Node agora suporta spawn e exec síncrono. Todos os methods descritos acima são asynchronouss e têm uma contraparte síncrona. Documentação para eles pode ser encontrada aqui . Embora sejam úteis para o script, observe que, diferentemente dos methods usados ​​para gerar processos filho de forma assíncrona, os methods síncronos não retornam uma instância do ChildProcess .

Nó JS v10.9.0 , LTS v8.11.4 e v6.14.3 — agosto de 2018

Método asynchronous e adequado:

 'use strict'; const { spawn } = require( 'child_process' ), ls = spawn( 'ls', [ '-lh', '/usr' ] ); ls.stdout.on( 'data', data => { console.log( `stdout: ${data}` ); } ); ls.stderr.on( 'data', data => { console.log( `stderr: ${data}` ); } ); ls.on( 'close', code => { console.log( `child process exited with code ${code}` ); } ); 

Sincronizar:

 'use strict'; const { spawnSync } = require( 'child_process' ), ls = spawnSync( 'ls', [ '-lh', '/usr' ] ); console.log( `stderr: ${ls.stderr.toString()}` ); console.log( `stdout: ${ls.stdout.toString()}` ); 

Da documentação do Node.js v10.9.0

O mesmo vale para a documentação do Node.js v8.11.4 e a documentação do Node.js v6.14.3

Você está procurando child_process.exec

Aqui está o exemplo:

 const exec = require('child_process').exec; const child = exec('cat *.js bad_file | wc -l', (error, stdout, stderr) => { console.log(`stdout: ${stdout}`); console.log(`stderr: ${stderr}`); if (error !== null) { console.log(`exec error: ${error}`); } }); 
 const exec = require("child_process").exec exec("ls", (error, stdout, stderr) => { //do whatever here }) 

Acabei de escrever um ajudante Cli para lidar facilmente com o Unix / windows.

Javascript:

 define(["require", "exports"], function (require, exports) { /** * Helper to use the Command Line Interface (CLI) easily with both Windows and Unix environments. * Requires underscore or lodash as global through "_". */ var Cli = (function () { function Cli() {} /** * Execute a CLI command. * Manage Windows and Unix environment and try to execute the command on both env if fails. * Order: Windows -> Unix. * * @param command Command to execute. ('grunt') * @param args Args of the command. ('watch') * @param callback Success. * @param callbackErrorWindows Failure on Windows env. * @param callbackErrorUnix Failure on Unix env. */ Cli.execute = function (command, args, callback, callbackErrorWindows, callbackErrorUnix) { if (typeof args === "undefined") { args = []; } Cli.windows(command, args, callback, function () { callbackErrorWindows(); try { Cli.unix(command, args, callback, callbackErrorUnix); } catch (e) { console.log('------------- Failed to perform the command: "' + command + '" on all environments. -------------'); } }); }; /** * Execute a command on Windows environment. * * @param command Command to execute. ('grunt') * @param args Args of the command. ('watch') * @param callback Success callback. * @param callbackError Failure callback. */ Cli.windows = function (command, args, callback, callbackError) { if (typeof args === "undefined") { args = []; } try { Cli._execute(process.env.comspec, _.union(['/c', command], args)); callback(command, args, 'Windows'); } catch (e) { callbackError(command, args, 'Windows'); } }; /** * Execute a command on Unix environment. * * @param command Command to execute. ('grunt') * @param args Args of the command. ('watch') * @param callback Success callback. * @param callbackError Failure callback. */ Cli.unix = function (command, args, callback, callbackError) { if (typeof args === "undefined") { args = []; } try { Cli._execute(command, args); callback(command, args, 'Unix'); } catch (e) { callbackError(command, args, 'Unix'); } }; /** * Execute a command no matters what's the environment. * * @param command Command to execute. ('grunt') * @param args Args of the command. ('watch') * @private */ Cli._execute = function (command, args) { var spawn = require('child_process').spawn; var childProcess = spawn(command, args); childProcess.stdout.on("data", function (data) { console.log(data.toString()); }); childProcess.stderr.on("data", function (data) { console.error(data.toString()); }); }; return Cli; })(); exports.Cli = Cli; }); 

Arquivo de origem original datilografado:

  /** * Helper to use the Command Line Interface (CLI) easily with both Windows and Unix environments. * Requires underscore or lodash as global through "_". */ export class Cli { /** * Execute a CLI command. * Manage Windows and Unix environment and try to execute the command on both env if fails. * Order: Windows -> Unix. * * @param command Command to execute. ('grunt') * @param args Args of the command. ('watch') * @param callback Success. * @param callbackErrorWindows Failure on Windows env. * @param callbackErrorUnix Failure on Unix env. */ public static execute(command: string, args: string[] = [], callback ? : any, callbackErrorWindows ? : any, callbackErrorUnix ? : any) { Cli.windows(command, args, callback, function () { callbackErrorWindows(); try { Cli.unix(command, args, callback, callbackErrorUnix); } catch (e) { console.log('------------- Failed to perform the command: "' + command + '" on all environments. -------------'); } }); } /** * Execute a command on Windows environment. * * @param command Command to execute. ('grunt') * @param args Args of the command. ('watch') * @param callback Success callback. * @param callbackError Failure callback. */ public static windows(command: string, args: string[] = [], callback ? : any, callbackError ? : any) { try { Cli._execute(process.env.comspec, _.union(['/c', command], args)); callback(command, args, 'Windows'); } catch (e) { callbackError(command, args, 'Windows'); } } /** * Execute a command on Unix environment. * * @param command Command to execute. ('grunt') * @param args Args of the command. ('watch') * @param callback Success callback. * @param callbackError Failure callback. */ public static unix(command: string, args: string[] = [], callback ? : any, callbackError ? : any) { try { Cli._execute(command, args); callback(command, args, 'Unix'); } catch (e) { callbackError(command, args, 'Unix'); } } /** * Execute a command no matters what's the environment. * * @param command Command to execute. ('grunt') * @param args Args of the command. ('watch') * @private */ private static _execute(command, args) { var spawn = require('child_process').spawn; var childProcess = spawn(command, args); childProcess.stdout.on("data", function (data) { console.log(data.toString()); }); childProcess.stderr.on("data", function (data) { console.error(data.toString()); }); } } Example of use: Cli.execute(Grunt._command, args, function (command, args, env) { console.log('Grunt has been automatically executed. (' + env + ')'); }, function (command, args, env) { console.error('------------- Windows "' + command + '" command failed, trying Unix... ---------------'); }, function (command, args, env) { console.error('------------- Unix "' + command + '" command failed too. ---------------'); }); 

Se você quiser algo que se pareça com a resposta principal, mas que também seja síncrono, isso funcionará.

 var execSync = require('child_process').execSync; var cmd = "echo 'hello world'"; var options = { encoding: 'utf8' }; console.log(execSync(cmd, options)); 

Desde a versão 4, a alternativa mais próxima é o método child_process.execSync :

 const execSync = require('child_process').execSync; var cmd = execSync('prince -v builds/pdf/book.html -o builds/pdf/book.pdf'); 

Observe que esse método bloqueia o loop de events.

@ A resposta do hexacianeto é quase completa. No Windows, o comando prince poderia ser prince.exe , prince.cmd , prince.bat ou apenas prince (não sei como as gemas são empacotadas, mas as npm bins vêm com um script sh e um script em lote – npm e npm.cmd ). Se você quiser escrever um script portátil que seria executado no Unix e no Windows, você precisa gerar o executável correto.

Aqui está uma function de desova simples, mas portátil:

 function spawn(cmd, args, opt) { var isWindows = /win/.test(process.platform); if ( isWindows ) { if ( !args ) args = []; args.unshift(cmd); args.unshift('/c'); cmd = process.env.comspec; } return child_process.spawn(cmd, args, opt); } var cmd = spawn("prince", ["-v", "builds/pdf/book.html", "-o", "builds/pdf/book.pdf"]) // Use these props to get execution results: // cmd.stdin; // cmd.stdout; // cmd.stderr;