Exécutez un binaire de ligne de commande avec Node.js

649

Je suis en train de porter une bibliothèque CLI de Ruby vers Node.js. Dans mon code, j'exécute plusieurs binaires tiers lorsque cela est nécessaire. Je ne sais pas comment accomplir cela au mieux dans Node.

Voici un exemple dans Ruby où j'appelle PrinceXML pour convertir un fichier en PDF:

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

Quel est le code équivalent dans Node?

Dave Thompson
la source
3
Cette bibliothèque est un bon point de départ. Il vous permet de générer des processus sur toutes les plates-formes OS.
Obsidian
2
Le plus simple est d'utiliser child_process.exec, voici quelques bons exemples
drorw

Réponses:

1070

Pour une version encore plus récente de Node.js (v8.1.4), les événements et les appels sont similaires ou identiques aux versions antérieures, mais il est encouragé d'utiliser les fonctionnalités de langage plus récentes standard. Exemples:

Pour une sortie mise en mémoire tampon et non formatée (vous obtenez tout cela en même temps), utilisez 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}`);
});

Vous pouvez également l'utiliser avec 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();

Si vous souhaitez recevoir les données progressivement en morceaux (sortie sous forme de flux), utilisez 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}`);
});

Ces deux fonctions ont une contrepartie synchrone. Un exemple pour 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');

Ainsi que 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);

Remarque: Le code suivant est toujours fonctionnel, mais s'adresse principalement aux utilisateurs d'ES5 et versions antérieures.

Le module de génération de processus enfants avec Node.js est bien documenté dans la documentation (v5.0.0). Pour exécuter une commande et récupérer sa sortie complète sous forme de tampon, utilisezchild_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 vous devez utiliser la gestion des E / S de processus avec des flux, par exemple lorsque vous attendez de grandes quantités de sortie, utilisez 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 vous exécutez un fichier plutôt qu'une commande, vous voudrez peut-être utiliser des child_process.execFileparamètres qui sont presque identiques à spawn, mais qui ont un quatrième paramètre de rappel comme execpour récupérer les tampons de sortie. Cela pourrait ressembler un peu à ceci:

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

Depuis la v0.11.12 , Node prend désormais en charge les modes synchrone spawnet exec. Toutes les méthodes décrites ci-dessus sont asynchrones et ont une contrepartie synchrone. La documentation pour eux peut être trouvée ici . Bien qu'elles soient utiles pour les scripts, notez que contrairement aux méthodes utilisées pour générer des processus enfants de manière asynchrone, les méthodes synchrones ne renvoient pas d'instance de ChildProcess.

hexacyanide
la source
19
MERCI. Cela me rendait fou. Parfois, il est utile de simplement signaler la solution évidente afin que nous, noobs (au nœud), puissions l'apprendre et l'exécuter.
Dave Thompson
10
Remarque: require ('child_process'). ExecFile () sera intéressant pour les personnes qui ont besoin d'exécuter un fichier plutôt qu'une commande connue à l'échelle du système comme prince ici.
Louis Ameline
2
Au lieu de child.pipe(dest)(qui n'existe pas), vous devez utiliser child.stdout.pipe(dest)et child.stderr.pipe(dest), par exemple child.stdout.pipe(process.stdout)et child.stderr.pipe(process.stderr).
ComFreek
Que se passe-t-il si je ne veux pas tout mettre dans un fichier, mais je veux exécuter plus d'une commande? Peut-être comme echo "hello"et echo "world".
Cameron
est-ce la manière standard de procéder? je veux dire comment tous les wrappers sont écrits en nodejs? je veux dire, disons pour gearman, rabbitmq etc. qui nécessitent d'exécuter la commande mais ils ont aussi du wrapper mais je ne trouve aucun de ce code dans leur code de bibliothèque
ANinJa
261

Noeud JS v13.9.0, LTS v12.16.1etv10.19.0 --- mars 2020

Méthode Async (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éthode asynchrone (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}` ) );


Sync:

'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 documentation Node.js v13.9.0

Il en va de même pour la documentation Node.js v12.16.1 et la documentation Node.js v10.19.0

iSkore
la source
8
Merci d'avoir donné des versions correctes et simples. La version de synchronisation un peu plus simple était tout à fait appropriée pour mon script "faire quelque chose et le jeter" dont j'avais besoin.
Brian Jorden
Aucun problème! Toujours agréable d'avoir les deux même si ce n'est pas "correct" selon certains.
iSkore du
7
Il pourrait être utile de souligner que pour faire cet exemple sous Windows, il faut utiliser 'cmd', ['/c', 'dir']. Au moins, je cherchais juste haut et bas pourquoi 'dir'sans arguments ne fonctionne pas avant de m'en souvenir ...;)
AndyO
1
Aucun de ces éléments n'a rien RENDU à la console.
Tyguy7
@ Tyguy7 comment le gérez-vous? Et avez-vous des remplacements sur l'objet console?
iSkore
73

Vous recherchez child_process.exec

Voici l'exemple:

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
la source
C'est correct. Mais sachez que ce type d'appel d'un processus enfant a des limites pour la longueur de stdout.
hgoebl
@hgoebl, quelle est alors l'alternative?
Harshdeep du
2
@Harshdeep en cas de sorties stdout longues (plusieurs Mo par exemple), vous pouvez écouter les dataévénements sur stdout. Regardez dans les documents, mais ça doit être quelque chose comme ça childProc.stdout.on("data", fn).
hgoebl
30
const exec = require("child_process").exec
exec("ls", (error, stdout, stderr) => {
 //do whatever here
})
Ben Bieler
la source
14
Veuillez ajouter plus d'explications sur la façon dont ce code fonctionne et comment il résout la réponse. N'oubliez pas que StackOverflow crée une archive de réponses pour les personnes qui le liront à l'avenir.
Al Sweigart
4
Ce que Al a dit est vrai, mais je dirai que l'avantage de cette réponse est qu'elle est tellement plus simple que d'avoir à lire la première réponse pour quelqu'un qui a besoin d'une réponse rapide.
29

Depuis la version 4, l'alternative la plus proche est la child_process.execSyncméthode:

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

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

Notez que l' execSyncappel bloque la boucle d'événements.

Paul Rumkin
la source
Cela fonctionne très bien sur le dernier nœud. Est-ce qu'un child_processêtre est créé lors de l'utilisation execSync? Et est-il supprimé juste après la commande, non? Donc pas de fuite de mémoire?
NiCk Newman
1
Oui, il n'y a aucune fuite de mémoire. Je suppose qu'il initialise uniquement les structures de processus enfant libuv sans le créer dans le nœud du tout.
Paul Rumkin
21

Si vous voulez quelque chose qui ressemble étroitement à la première réponse mais qui est également synchrone, cela fonctionnera.

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

var options = {
  encoding: 'utf8'
};

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

Je viens d'écrire un assistant Cli pour gérer facilement 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;
});

Fichier source original dactylographié:

 /**
 * 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
la source
1
Version la plus récente là-bas, avec un exemple d'utilisation pour utiliser Grunt dans CLI: gist.github.com/Vadorequest/f72fa1c152ec55357839
Vadorequest
6

Si cela ne vous dérange pas une dépendance et que vous souhaitez utiliser des promesses, child-process-promisefonctionne:

installation

npm install child-process-promise --save

Utilisation d'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);
    });

utilisation du frai

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);
    });
Des mots comme Jared
la source
6

Vous pouvez maintenant utiliser shelljs (à partir du nœud v4) comme suit:

var shell = require('shelljs');

shell.echo('hello world');
shell.exec('node --version')
FacePalm
la source
Il ne devrait pas être nécessaire d'installer de nouveaux modules
Ben Bieler Il y a
4

Utilisez ce npmpackage léger :system-commands

Regardez-le ici .

Importez-le comme ceci:

const system = require('system-commands')

Exécutez des commandes comme celle-ci:

system('ls').then(output => {
    console.log(output)
}).catch(error => {
    console.error(error)
})
Ken Mueller
la source
Parfait! Fonctionne très bien pour mes besoins.
roosevelt
3

La réponse de @ hexacyanide est presque complète. Sur commande Windows princepourrait être prince.exe, prince.cmd, prince.batou tout simplement prince(je ne suis pas au courant de la façon dont les pierres précieuses sont empaquetés, mais les bacs MNP sont livrés avec un script sh et un script de traitement par lots - npmet npm.cmd). Si vous voulez écrire un script portable qui s'exécuterait sur Unix et Windows, vous devez générer le bon exécutable.

Voici une fonction d'apparition simple mais portable:

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
la source