Виконайте бінарний командний рядок за допомогою Node.js


648

Я переношу бібліотеку CLI з Ruby на Node.js. У своєму коді я виконую декілька сторонніх двійкових файлів, коли це необхідно. Я не впевнений, як найкраще досягти цього в Node.

Ось приклад у Ruby, де я закликаю PrinceXML для перетворення файлу в PDF:

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

Що таке еквівалентний код у Вузлі?


3
Ця бібліотека - гарне місце для початку. Це дозволяє нерестувати процеси на всіх платформах OS.
Обсидіан


2
Найпростішим є використання child_process.exec, ось кілька хороших прикладів
drorw

Відповіді:


1069

Для навіть більш нової версії Node.js (v8.1.4) події та виклики схожі або ідентичні старішим версіям, але рекомендується використовувати стандартні новіші мовні функції. Приклади:

Для буферованого вихідного форматованого виводу (ви отримуєте все це відразу), використовуйте 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}`);
});

Ви також можете використовувати його з Обіцяннями:

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

Якщо ви хочете отримувати дані поступово в шматки (виводити як потік), використовуйте 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}`);
});

Обидві ці функції мають синхронний аналог. Приклад для 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');

А також 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);

Примітка . Наступний код все ще функціонує, але в першу чергу орієнтований на користувачів ES5 та раніше.

Модуль для нерестування дочірніх процесів з Node.js добре зафіксований у документації (v5.0.0). Щоб виконати команду та отримати її повний вихід у вигляді буфера, використовуйте 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
});

Якщо вам потрібно використовувати обробку вводу-виводу з потоками, наприклад, коли ви очікуєте великої кількості виводу, використовуйте 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);

Якщо ви виконуєте файл, а не команду, ви можете використовувати child_process.execFileпараметри, які майже ідентичні spawn, але мають четвертий параметр зворотного виклику, як execдля отримання вихідних буферів. Це може виглядати приблизно так:

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

Станом на v0.11.12 , Node тепер підтримує синхронні spawnта exec. Усі описані вище методи є асинхронними та мають синхронний аналог. Документацію на них можна знайти тут . Хоча вони корисні для сценаріїв, зауважте, що на відміну від методів, що використовуються для нерестування дочірніх процесів асинхронно, синхронні методи не повертають примірник ChildProcess.


19
ДЯКУЮ ТОБІ. Це ганяло мене. Іноді допомагає просто вказати на очевидне рішення, щоб ми, ноуби, могли навчитися і працювати з ним.
Дейв Томпсон

10
Примітка: вимагати ('child_process'). ExecFile () буде цікавим для людей, яким потрібно запустити файл, а не загальновідому команду, як Prince тут.
Луї Амлін

2
Замість child.pipe(dest)(якого не існує), ви повинні використовувати child.stdout.pipe(dest)і child.stderr.pipe(dest), наприклад, child.stdout.pipe(process.stdout)і child.stderr.pipe(process.stderr).
ComFreek

Що робити, якщо я не хочу все вміщувати у файл, але хочу виконати більше однієї команди? Можливо, як echo "hello"і echo "world".
Камерон

це стандартний спосіб це зробити? я маю на увазі, як усі обгортки написані в nodejs? я маю на увазі, скажімо, для gearman, rabbitmq і т. д., які потребують виконання команди, але у них також є якась обгортка, але я не можу знайти жодного з цих кодів у своєму бібліотечному коді
ANinJa

261

Вузол JS v13.9.0, LTS v12.16.1та v10.19.0 --- березня 2020 року

Метод асинхронізації (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}` );
} );


Метод асинхронізації (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}` ) );


Синхронізація:

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

Від Node.js v13.9.0 Документація

Те саме стосується Node.js v12.16.1 Документація та Node.js v10.19.0 Документація


8
Дякуємо, що надали як правильні, так і прості версії. Трохи простіша версія синхронізації була цілком чудова для мого сценарію "зроби щось і відкинь", який мені потрібен.
Брайан Йорден

Нема проблем! Завжди приємно мати обох, навіть якщо це не є "належним".
iSkore

7
Можливо, варто зазначити, що для того, щоб зробити цей приклад в Windows, потрібно використовувати 'cmd', ['/c', 'dir']. Принаймні, я просто шукав високо і низько, чому 'dir'без аргументів не виходить, перш ніж я згадав про це ...;)
AndyO

1
Жоден з цих виходів НІЧОГО на консоль.
Tyguy7

@ Tyguy7 як ти це працюєш? А у вас є якісь зміни на об'єкті консолі?
iSkore

73

Ви шукаєте child_process.exec

Ось приклад:

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

Це вірно. Але майте на увазі, що подібний виклик дочірнього процесу має обмеження щодо тривалості стрибування.
hgoebl

@hgoebl, яка альтернатива тоді?
Харшдейп

2
@Harshdeep у випадку тривалих виходів stdout (кілька Мб, наприклад) ви можете слухати dataподії в stdout. Подивіться в документи, але це повинно бути щось на зразок childProc.stdout.on("data", fn).
hgoebl

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

14
Будь ласка, додайте більше пояснень, як працює цей код і як він вирішує відповідь. Пам'ятайте, що StackOverflow створює архів відповідей для людей, які читають це в майбутньому.
Al Sweigart

4
Те, що сказав Ал, правдиве, але я скажу, що користь цієї відповіді полягає в тому, що це набагато простіше, ніж потрібно прочитати верхню відповідь для того, хто потребує швидкої відповіді.

29

Оскільки у версії 4 найближчою альтернативою є child_process.execSyncметод:

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

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

Зауважте, що execSyncвиклик блокує цикл подій.


Це чудово працює на останньому вузлі. Чи створюється child_processістота при використанні execSync? І чи видаляється вона відразу після команди, правда? Тож ніякої пам'яті не протікає?
NiCk Newman

1
Так, немає витоків пам'яті. Я думаю, він ініціалізує лише структури дочірніх процесів libuv, не створюючи їх у вузлі взагалі.
Пол Румкін

21

Якщо ви хочете щось, що дуже нагадує основну відповідь, але також синхронне, це спрацює.

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

var options = {
  encoding: 'utf8'
};

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

14

Я щойно написав помічника клієнта, щоб легко працювати з 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;
});

Первинний вихідний файл машинопису:

 /**
 * 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
Найновіша версія там, із прикладом використання для використання Grunt в CLI: gist.github.com/Vadorequest/f72fa1c152ec55357839
Vadorequest

7

Тепер ви можете використовувати shelljs (з вузла v4) наступним чином:

var shell = require('shelljs');

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

6

Якщо ви не заперечуєте проти залежності і хочете використовувати обіцянки, child-process-promiseпрацює:

установка

npm install child-process-promise --save

Використання

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

використання нересту

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

4

Скористайтеся цим легким npmпакетом:system-commands

Подивіться це тут .

Імпортуйте його так:

const system = require('system-commands')

Запускайте такі команди:

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

Ідеально! Відмінно працює для моїх потреб.
roosevelt

3

@ відповідь гексаціаніду майже повна. У командному рядку Windows princeможе бути prince.exe, prince.cmd, prince.batабо просто prince(я не в курсі , як дорогоцінні камені в комплекті, але НПЕ контейнери поставляються з скриптом ш і пакетним сценарієм - npmі npm.cmd). Якщо ви хочете написати портативний скрипт, який би виконувався в Unix та Windows, вам доведеться породити потрібний виконуваний файл.

Ось проста, але портативна функція нересту:

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;
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.