Ejecute un binario de línea de comando con Node.js

649

Estoy en el proceso de portar una biblioteca CLI de Ruby a Node.js. En mi código ejecuto varios binarios de terceros cuando es necesario. No estoy seguro de cómo lograr esto en Node.

Aquí hay un ejemplo en Ruby donde llamo a PrinceXML para convertir un archivo a PDF:

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

¿Cuál es el código equivalente en el nodo?

Dave Thompson
fuente
3
Esta biblioteca es un buen lugar para comenzar. Le permite generar procesos en todas las plataformas del sistema operativo.
Obsidiana
2
Lo más simple es usar child_process.exec, aquí hay algunos buenos ejemplos
drorw

Respuestas:

1070

Para una versión aún más nueva de Node.js (v8.1.4), los eventos y las llamadas son similares o idénticos a las versiones anteriores, pero se recomienda utilizar las funciones estándar de lenguaje más nuevas. Ejemplos:

Para la salida con formato no almacenado en búfer (se obtiene todo de una 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}`);
});

También puedes usarlo con Promesas:

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

Si desea recibir los datos gradualmente en fragmentos (salida como una secuencia), 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 funciones tienen una contraparte síncrona. Un ejemplo 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');

Además de 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: El siguiente código sigue siendo funcional, pero está dirigido principalmente a usuarios de ES5 y anteriores.

El módulo para generar procesos secundarios con Node.js está bien documentado en la documentación (v5.0.0). Para ejecutar un comando y obtener su salida completa como un búfer, 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
});

Si necesita utilizar la E / S del proceso de manejo con flujos, como cuando espera grandes cantidades de salida, 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);

Si está ejecutando un archivo en lugar de un comando, es posible que desee utilizar child_process.execFilequé parámetros son casi idénticos spawn, pero tiene un cuarto parámetro de devolución de llamada como execpara recuperar buffers de salida. Eso podría verse un poco así:

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

A partir de v0.11.12 , Node ahora admite síncronos spawny exec. Todos los métodos descritos anteriormente son asíncronos y tienen una contraparte síncrona. La documentación para ellos se puede encontrar aquí . Si bien son útiles para las secuencias de comandos, tenga en cuenta que, a diferencia de los métodos utilizados para generar procesos secundarios de forma asincrónica, los métodos sincrónicos no devuelven una instancia de ChildProcess.

hexacianuro
fuente
19
GRACIAS. Esto me estaba volviendo loco. A veces es útil tener la solución obvia señalada para que los novatos (al nodo) podamos aprender y ejecutarla.
Dave Thompson
10
Nota: require ('child_process'). ExecFile () será de interés para las personas que necesitan ejecutar un archivo en lugar de un comando conocido en todo el sistema como prince aquí.
Louis Ameline
2
En lugar de child.pipe(dest)(que no existe), debe usar child.stdout.pipe(dest)y child.stderr.pipe(dest), por ejemplo, child.stdout.pipe(process.stdout)y child.stderr.pipe(process.stderr).
ComFreek
¿Qué sucede si no quiero poner todo en un archivo, pero quiero ejecutar más de un comando? Tal vez como echo "hello"y echo "world".
Cameron
¿Es esta la forma estándar de hacer esto? Quiero decir, ¿cómo se escriben todos los contenedores en nodejs? Quiero decir, digamos para gearman, rabbitmq, etc., que requieren ejecutar el comando, pero también tienen algo de contenedor, pero no puedo encontrar ninguno de este código en su código de biblioteca
ANinJa
261

Nodo JS v13.9.0, LTS v12.16.1y v10.19.0 --- Mar 2020

Método así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 así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}` ) );


Sincronización:

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

De Node.js v13.9.0 Documentation

Lo mismo ocurre con Node.js v12.16.1 Documentation y Node.js v10.19.0 Documentation

iSkore
fuente
8
Gracias por dar versiones correctas y simples. La versión de sincronización un poco más simple estaba totalmente bien para mi único script "haz algo y deséchalo" que necesitaba.
Brian Jorden
¡No hay problema! Siempre es bueno tener ambos, incluso si no es "adecuado" según algunos.
iSkore
77
Vale la pena señalar que para hacer este ejemplo en Windows, uno tiene que usar 'cmd', ['/c', 'dir']. Al menos solo buscaba por todas 'dir'
partes
1
Ninguno de estos produce NADA a la consola.
Tyguy7
@ Tyguy7 ¿cómo lo estás ejecutando? ¿Y tiene alguna anulación en el objeto de la consola?
iSkore
73

Usted busca child_process.exec

Aquí está el ejemplo:

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
fuente
Esto es correcto. Pero tenga en cuenta que este tipo de proceso secundario tiene limitaciones para la duración de stdout.
hgoebl
@hgoebl, ¿cuál es la alternativa entonces?
Harshdeep
2
@Harshdeep en caso de salidas stdout largas (varios MB, por ejemplo) puede escuchar dataeventos en stdout. Mira en los documentos, pero debe ser algo así childProc.stdout.on("data", fn).
hgoebl
30
const exec = require("child_process").exec
exec("ls", (error, stdout, stderr) => {
 //do whatever here
})
Ben Bieler
fuente
14
Agregue más explicaciones sobre cómo funciona este código y cómo resuelve la respuesta. Recuerde que StackOverflow está creando un archivo de respuestas para las personas que lean esto en el futuro.
Al Sweigart
44
Lo que dijo Al es cierto, pero diré que el beneficio de esta respuesta es que es mucho más simple que tener que leer la respuesta principal para alguien que necesita una respuesta rápida.
29

Desde la versión 4, la alternativa más cercana es el child_process.execSyncmétodo:

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

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

Tenga en cuenta que la execSyncllamada bloquea el bucle de eventos.

Paul Rumkin
fuente
Esto funciona muy bien en el último nodo. ¿Se child_processcrea un ser cuando se usa execSync? ¿Y se elimina justo después del comando, verdad? Entonces, ¿no hay pérdidas de memoria?
NiCk Newman
1
Sí, no hay pérdidas de memoria. Supongo que solo inicializa las estructuras de proceso hijo de libuv sin crearlo en el nodo.
Paul Rumkin
21

Si desea algo que se parezca mucho a la respuesta principal pero que también sea sincrónico, esto funcionará.

var execSync = require('child_process').execSync;
var cmd = "echo 'hello world'";

var options = {
  encoding: 'utf8'
};

console.log(execSync(cmd, options));
Cameron
fuente
14

Acabo de escribir un asistente de Cli para tratar con Unix / Windows fácilmente.

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

Archivo fuente original mecanografiado:

 /**
 * 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
fuente
1
La versión más reciente allí, con un ejemplo de uso para usar Grunt en CLI: gist.github.com/Vadorequest/f72fa1c152ec55357839
Vadorequest
6

Si no te importa una dependencia y quieres usar promesas, child-process-promise funciona:

instalación

npm install child-process-promise --save

uso ejecutivo

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);
    });
Palabras como Jared
fuente
6

Ahora puede usar shelljs (del nodo v4) de la siguiente manera:

var shell = require('shelljs');

shell.echo('hello world');
shell.exec('node --version')
FacePalm
fuente
No debería ser necesario instalar nuevos módulos
Ben Bieler
4

Use este npmpaquete liviano :system-commands

Míralo aquí .

Importarlo así:

const system = require('system-commands')

Ejecute comandos como este:

system('ls').then(output => {
    console.log(output)
}).catch(error => {
    console.error(error)
})
Ken Mueller
fuente
¡Perfecto! Funciona muy bien para mis necesidades.
Roosevelt
3

La respuesta de @hexacyanide es casi completa. En Windows comando princepodría ser prince.exe, prince.cmd, prince.bato simplemente prince(no soy consciente de cómo se agrupan las gemas, pero NGP contenedores vienen con un script sh y una escritura de la hornada - npmynpm.cmd ). Si desea escribir un script portátil que se ejecute en Unix y Windows, debe generar el ejecutable correcto.

Aquí hay una función de generación simple pero 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
fuente