Eseguire un file binario della riga di comando con Node.js


649

Sto eseguendo il porting di una libreria CLI da Ruby a Node.js. Nel mio codice eseguo diversi file binari di terze parti quando necessario. Non sono sicuro del modo migliore per farlo in Node.

Ecco un esempio in Ruby in cui chiamo PrinceXML per convertire un file in PDF:

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

Qual è il codice equivalente in Node?


3
Questa libreria è un buon punto di partenza. Ti consente di generare processi su tutte le piattaforme OS.
Obsidian,


2
La cosa più semplice è usare child_process.exec, ecco alcuni buoni esempi
drorw,

Risposte:


1070

Per la versione ancora più recente di Node.js (v8.1.4), gli eventi e le chiamate sono simili o identici alle versioni precedenti, ma si consiglia di utilizzare le funzionalità del linguaggio più recenti standard. Esempi:

Per l'output formattato buffer, non stream (ottieni tutto in una volta), usa 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}`);
});

Puoi anche usarlo con 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 si desidera ricevere i dati gradualmente in blocchi (output come flusso), utilizzare 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}`);
});

Entrambe queste funzioni hanno una controparte sincrona. Un esempio per 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');

Oltre a 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: il seguente codice è ancora funzionante, ma è principalmente destinato agli utenti di ES5 e precedenti.

Il modulo per la generazione di processi figlio con Node.js è ben documentato nella documentazione (v5.0.0). Per eseguire un comando e recuperare l'output completo come buffer, utilizzare 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 è necessario utilizzare l'I / O del processo di gestione con flussi, ad esempio quando si prevedono grandi quantità di output, utilizzare 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 si sta eseguendo un file anziché un comando, è possibile che si desideri utilizzare child_process.execFilequali parametri sono quasi identici spawn, ma ha un quarto parametro di callback come execper il recupero dei buffer di output. Potrebbe apparire un po 'così:

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

A partire da v0.11.12 , Node ora supporta sincrono spawne exec. Tutti i metodi sopra descritti sono asincroni e hanno una controparte sincrona. La documentazione per loro può essere trovata qui . Sebbene siano utili per gli script, tenere presente che, diversamente dai metodi utilizzati per generare i processi figlio in modo asincrono, i metodi sincroni non restituiscono un'istanza di ChildProcess.


19
GRAZIE. Questo mi stava facendo impazzire. A volte aiuta solo a mettere in evidenza la soluzione ovvia in modo che noi (per nodo) possiamo imparare e correre con esso.
Dave Thompson,

10
Nota: request ('child_process'). ExecFile () sarà di interesse per le persone che hanno bisogno di eseguire un file piuttosto che un comando noto a livello di sistema come il principe qui.
Louis Ameline,

2
Invece di child.pipe(dest)(che non esiste), devi usare child.stdout.pipe(dest)e child.stderr.pipe(dest), ad esempio child.stdout.pipe(process.stdout)e child.stderr.pipe(process.stderr).
ComFreek,

Cosa succede se non voglio inserire tutto in un file, ma voglio eseguire più di un comando? Forse mi piace echo "hello"e echo "world".
Cameron,

è questo il modo standard per farlo? intendo come sono scritti tutti i wrapper in nodejs? Voglio dire, diciamo per gearman, rabbitmq ecc. che richiedono di eseguire il comando ma hanno anche un po 'di wrapper ma non riesco a trovare nessuno di questo codice nel loro codice di libreria
ANinJa

261

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

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


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

Dalla documentazione di Node.js v13.9.0

Lo stesso vale per la documentazione Node.js v12.16.1 e la documentazione Node.js v10.19.0


8
Grazie per aver dato entrambe le versioni corrette e semplici. La versione di sincronizzazione leggermente più semplice andava benissimo per il mio script "fai qualcosa e buttalo via" di cui avevo bisogno.
Brian Jorden,

Nessun problema! Sempre bello avere entrambi anche se non è "corretto" secondo alcuni.
iSkore,

7
Potrebbe valere la pena sottolineare che per fare questo esempio in Windows, è necessario utilizzare 'cmd', ['/c', 'dir']. Almeno stavo solo cercando in alto e in basso perché 'dir'senza argomenti non funziona prima di ricordare questo ...;)
AndyO

1
Nessuno di questi ha prodotto ANYTHING sulla console.
Tyguy7,

@ Tyguy7 come lo stai eseguendo? E hai delle sostituzioni sull'oggetto console?
iSkore,

73

Stai cercando child_process.exec

Ecco l'esempio:

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

Questo è corretto. Ma tieni presente che questo tipo di chiamare un processo figlio ha delle limitazioni per la durata dello stdout.
hgoebl,

@hgoebl, qual è l'alternativa allora?
Harshdeep,

2
@Harshdeep in caso di uscite stdout lunghe (ad esempio diversi MB) è possibile ascoltare gli dataeventi su stdout. Guarda nei documenti, ma deve essere qualcosa di simile childProc.stdout.on("data", fn).
hgoebl,

30
const exec = require("child_process").exec
exec("ls", (error, stdout, stderr) => {
 //do whatever here
})

14
Aggiungi ulteriori spiegazioni su come funziona questo codice e su come risolve la risposta. Ricorda che StackOverflow sta costruendo un archivio di risposte per le persone che leggeranno questo in futuro.
Al Sweigart,

4
Quello che Al ha detto è vero, ma dirò che il vantaggio di questa risposta è che è molto più semplice che dover leggere la risposta migliore per qualcuno che ha bisogno di una risposta rapida.

29

Dalla versione 4 l'alternativa più vicina è il child_process.execSyncmetodo:

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

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

Si noti che il execSyncblocco degli eventi viene richiamato.


Funziona alla grande sull'ultimo nodo. Un child_processessere creato durante l'utilizzo execSyncperò? E viene rimosso subito dopo il comando, giusto? Quindi nessuna perdita di memoria?
NiCk Newman,

1
Sì, non ci sono perdite di memoria. Immagino che inizializzi solo le strutture del processo figlio libuv senza crearle nel nodo.
Paul Rumkin,

21

Se vuoi qualcosa che assomigli molto alla risposta principale ma sia anche sincrono, allora funzionerà.

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

var options = {
  encoding: 'utf8'
};

console.log(execSync(cmd, options));

14

Ho appena scritto un helper Cli per gestire facilmente 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;
});

File sorgente originale dattiloscritto:

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

1
Versione più recente lì, con esempio di utilizzo per utilizzare Grunt nella CLI: gist.github.com/Vadorequest/f72fa1c152ec55357839
Vadorequest

6

Se non ti dispiace una dipendenza e vuoi usare le promesse, child-process-promisefunziona:

installazione

npm install child-process-promise --save

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

spawn use

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

6

Ora puoi usare shelljs (dal nodo v4) come segue:

var shell = require('shelljs');

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

Non dovrebbe essere necessario installare nuovi moduli
Ben Bieler

4

Usa questo npmpacchetto leggero :system-commands

Guarda qui .

Importalo in questo modo:

const system = require('system-commands')

Esegui comandi in questo modo:

system('ls').then(output => {
    console.log(output)
}).catch(error => {
    console.error(error)
})

Perfetto! Funziona alla grande per le mie esigenze.
roosevelt,

3

La risposta di hexacyanide è quasi completa. Su comando di Windows princepotrebbe essere prince.exe, prince.cmd, prince.bato solo prince(io non sono a conoscenza di come gemme sono in bundle, ma bidoni NPM vengo con uno script sh e uno script batch - npme npm.cmd). Se vuoi scrivere uno script portatile che verrebbe eseguito su Unix e Windows, devi generare l'eseguibile giusto.

Ecco una funzione di spawn semplice ma portatile:

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;
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.