Execute a command line binary with Node.js

2018-12-31 07:11发布

I am in the process of porting a CLI library from Ruby over to Node.js. In my code I execute several third party binaries when necessary. I am not sure how best to accomplish this in Node.

Here's an example in Ruby where I call PrinceXML to convert a file to a PDF:

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

What is the equivalent code in Node?

9条回答
宁负流年不负卿
2楼-- · 2018-12-31 07:26

I just wrote a Cli helper to deal with Unix/windows easily.

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

Typescript original source file:

 /**
 * 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. ---------------');
    });
查看更多
余生无你
3楼-- · 2018-12-31 07:27

For even newer version of Node.js (v8.1.4), the events and calls are similar or identical to older versions, but it's encouraged to use the standard newer language features. Examples:

For buffered, non-stream formatted output (you get it all at once), 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}`);
});

You can also use it with 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();

If you wish to receive the data gradually in chunks (output as a 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}`);
});

Both of these functions have a synchronous counterpart. An example for 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');

As well as 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);

Note: The following code is still functional, but is primarily targeted at users of ES5 and before.

The module for spawning child processes with Node.js is well documented in the documentation (v5.0.0). To execute a command and fetch its complete output as a 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
});

If you need to use handle process I/O with streams, such as when you are expecting large amounts of output, 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);

If you are executing a file rather than a command, you might want to use child_process.execFile, which parameters which are almost identical to spawn, but has a fourth callback parameter like exec for retrieving output buffers. That might look a bit like this:

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

As of v0.11.12, Node now supports synchronous spawn and exec. All of the methods described above are asynchronous, and have a synchronous counterpart. Documentation for them can be found here. While they are useful for scripting, do note that unlike the methods used to spawn child processes asynchronously, the synchronous methods do not return an instance of ChildProcess.

查看更多
低头抚发
4楼-- · 2018-12-31 07:39

Node JS v11.5.0, LTS v10.14.2, and v8.14.1 --- Dec 2018

Async and proper method (Unix):

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


Async method (Windows):

'use strict';

const
    { spawn } = require( 'child_process' ),
    dir = spawn( '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' ),
    ls = spawnSync( 'ls', [ '-lh', '/usr' ] );

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

From Node.js v11.5.0 Documentation

The same goes for Node.js v10.14.2 Documentation and Node.js v8.14.1 Documentation

查看更多
君临天下
5楼-- · 2018-12-31 07:39

You are looking for child_process.exec

Here is the example:

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}`);
        }
});
查看更多
呛了眼睛熬了心
6楼-- · 2018-12-31 07:40

If you want something that closely resembles the top answer but is also synchronous then this will work.

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

var options = {
  encoding: 'utf8'
};

console.log(execSync(cmd, options));
查看更多
深知你不懂我心
7楼-- · 2018-12-31 07:40

Since version 4 the closest alternative is child_process.execSync method:

const execSync = require('child_process').execSync;

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

Note that this method blocks event loop.

查看更多
登录 后发表回答