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

648

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

Aqui está um exemplo no Ruby em que 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 Nó?

Dave Thompson
fonte
3
Esta biblioteca é um bom lugar para começar. Ele permite gerar processos em todas as plataformas OS.
Obsidian
Possível duplicado de Executar e obter a saída de um comando shell no node.js
Damjan Pavlica
2
Mais simples é usar child_process.exec, aqui estão alguns exemplos bons
drorw

Respostas:

1069

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

Para saída formatada em buffer e sem fluxo (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 o 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 fluxo), 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 as funções têm uma contraparte síncrona. Um exemplo para child_process.execSync:

const { execSync } = require('child_process');
// stderr is sent to stderr 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 gerar 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 E / S de processo de manipulação com fluxos, como quando espera 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, convém usar child_process.execFilequais parâmetros são quase idênticos spawn, mas tem um quarto parâmetro de retorno de chamada como execpara recuperar buffers de saída. Isso pode parecer um pouco com isso:

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 Nó agora suporta síncrono spawne exec. Todos os métodos descritos acima são assíncronos e têm uma contrapartida síncrona. A documentação para eles pode ser encontrada aqui . Embora sejam úteis para scripts, observe que, diferentemente dos métodos usados ​​para gerar processos filho de forma assíncrona, os métodos síncronos não retornam uma instância de ChildProcess.

hexacianida
fonte
19
OBRIGADO. Isso estava me deixando louco. Às vezes, ajuda apenas apontar a solução óbvia para que nós, noobs, possamos aprender e executar com ela.
Dave Thompson
10
Nota: require ('child_process'). ExecFile () será de interesse para as pessoas que precisam executar um arquivo em vez de um comando conhecido em todo o sistema, como o príncipe aqui.
Louis Ameline
2
Em vez de child.pipe(dest)(que não existe), você deve usar child.stdout.pipe(dest)e child.stderr.pipe(dest), por exemplo, child.stdout.pipe(process.stdout)e child.stderr.pipe(process.stderr).
ComFreek
E se eu não quiser colocar tudo em um arquivo, mas desejar executar mais de um comando? Talvez goste echo "hello"e echo "world".
Cameron
essa é a maneira padrão de fazer isso? Quero dizer, como todo o wrapper é escrito em nodejs? digamos que quero dizer Vamos para gearman, RabbitMQ etc, que exigem para executar o comando, mas eles também têm algum invólucro tão bem, mas eu não posso encontrar qualquer deste código em seu código da biblioteca
ANinJa
261

Nó JS v13.9.0, LTS v12.16.1e v10.19.0 --- Mar 2020

Método assíncrono (Unix):

'use strict';

const { spawn } = require( 'child_process' );
const 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}` );
} );


Método assíncrono (Windows):

'use strict';

const { spawn } = require( 'child_process' );
const dir = spawn('cmd', ['/c', 'dir'])

dir.stdout.on( 'data', data => console.log( `stdout: ${data}` ) );
dir.stderr.on( 'data', data => console.log( `stderr: ${data}` ) );
dir.on( 'close', code => console.log( `child process exited with code ${code}` ) );


Sincronizar:

'use strict';

const { spawnSync } = require( 'child_process' );
const ls = spawnSync( 'ls', [ '-lh', '/usr' ] );

console.log( `stderr: ${ls.stderr.toString()}` );
console.log( `stdout: ${ls.stdout.toString()}` );

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

O mesmo vale para Node.js Documentação v12.16.1 e Documentação v10.19.0 Node.js

iSkore
fonte
8
Obrigado por fornecer as versões correta e simples. A versão de sincronização um pouco mais simples foi excelente para o script "faça algo e jogue fora" de que eu precisava.
precisa saber é o seguinte
Sem problemas! Sempre bom ter os dois, mesmo que não seja "adequado", de acordo com alguns.
ISkore 15/04
7
Vale a pena ressaltar que, para fazer esse exemplo no Windows, é preciso usar 'cmd', ['/c', 'dir']. Pelo menos eu estava apenas procura alta e baixa porque 'dir'sem argumentos não trabalho antes de me lembrar isso ...;)
AndyO
1
Nada disso gera QUALQUER COISA para o console.
precisa saber é o seguinte
@ Tyguy7 como você está executando? E você tem alguma substituição no objeto do console?
iSkore 15/09/18
73

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}`);
        }
});
Andrei Karpushonak
fonte
Isto está correto. Mas esteja ciente de que esse tipo de chamada de processo filho tem limitações para a duração do stdout.
hgoebl
@ hgoebl, qual é a alternativa então?
Harshdeep
2
@Harshdeep no caso de saídas stdout longas (vários MB, por exemplo), você pode ouvir dataeventos no stdout. Procure nos documentos, mas deve ser algo parecido childProc.stdout.on("data", fn).
hgoebl
30
const exec = require("child_process").exec
exec("ls", (error, stdout, stderr) => {
 //do whatever here
})
Ben Bieler
fonte
14
Adicione mais explicações sobre como esse código funciona e como resolve a resposta. Lembre-se de que o StackOverflow está construindo um arquivo de respostas para as pessoas que estiverem lendo isso no futuro.
Al Sweigart
4
O que Al disse é verdade, mas direi que o benefício desta resposta é que é muito mais simples do que ter que ler a resposta principal para alguém que precisa de uma resposta rápida.
29

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

const {execSync} = require('child_process');

let output = execSync('prince -v builds/pdf/book.html -o builds/pdf/book.pdf');

Observe que a execSyncchamada bloqueia o loop de eventos.

Paul Rumkin
fonte
Isso funciona muito bem no nó mais recente. É um child_processser criado quando se usa execSync? E ele é removido logo após o comando, certo? Então, não há vazamento de memória?
NiCk Newman
1
Sim, não há vazamentos de memória. Eu acho que ele inicializa apenas estruturas de processo filho libuv sem criá-lo no nó.
Paul Rumkin
21

Se você deseja algo que se assemelhe à resposta principal, mas 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));
Cameron
fonte
14

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

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. ---------------');
    });
Vadorequest
fonte
1
Versão mais recente lá, com exemplo de uso para usar o Grunt na CLI: gist.github.com/Vadorequest/f72fa1c152ec55357839
Vadorequest 23/15
7

Agora você pode usar shelljs (do nó v4) da seguinte maneira:

var shell = require('shelljs');

shell.echo('hello world');
shell.exec('node --version')
FacePalm
fonte
6

Se você não se importa com uma dependência e deseja usar promessas, child-process-promisefunciona:

instalação

npm install child-process-promise --save

uso de exec

var exec = require('child-process-promise').exec;

exec('echo hello')
    .then(function (result) {
        var stdout = result.stdout;
        var stderr = result.stderr;
        console.log('stdout: ', stdout);
        console.log('stderr: ', stderr);
    })
    .catch(function (err) {
        console.error('ERROR: ', err);
    });

uso de spawn

var spawn = require('child-process-promise').spawn;

var promise = spawn('echo', ['hello']);

var childProcess = promise.childProcess;

console.log('[spawn] childProcess.pid: ', childProcess.pid);
childProcess.stdout.on('data', function (data) {
    console.log('[spawn] stdout: ', data.toString());
});
childProcess.stderr.on('data', function (data) {
    console.log('[spawn] stderr: ', data.toString());
});

promise.then(function () {
        console.log('[spawn] done!');
    })
    .catch(function (err) {
        console.error('[spawn] ERROR: ', err);
    });
Words Like Jared
fonte
4

Use este npmpacote leve :system-commands

Veja aqui .

Importe-o assim:

const system = require('system-commands')

Execute comandos como este:

system('ls').then(output => {
    console.log(output)
}).catch(error => {
    console.error(error)
})
Ken Mueller
fonte
Perfeito! Funciona muito bem para as minhas necessidades.
roosevelt
3

A resposta da @ hexacyanide é quase completa. No comando do Windows princepoderia ser prince.exe, prince.cmd, prince.batou apenas prince(eu não sou ciente de como gemas são empacotados, mas bins NPM vêm com um script sh e um script em lotes - npme npm.cmd). Se você deseja escrever um script portátil que funcione no Unix e no Windows, é necessário gerar o executável correto.

Aqui está uma função de desova simples e 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;
DUzun
fonte