Uruchom plik binarny wiersza polecenia za pomocą Node.js


647

Jestem w trakcie przenoszenia biblioteki CLI z Ruby na Node.js. W moim kodzie w razie potrzeby wykonuję kilka plików binarnych stron trzecich. Nie jestem pewien, jak najlepiej to osiągnąć w węźle.

Oto przykład w Ruby, w którym wywołuję PrinceXML w celu konwersji pliku do formatu PDF:

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

Jaki jest równoważny kod w węźle?


3
Ta biblioteka jest dobrym miejscem do rozpoczęcia. Pozwala spawnować procesy na wszystkich platformach OS.
Obsidian


2
Najprościej jest użyć child_process.exec, oto kilka dobrych przykładów
drorw 18.04.19

Odpowiedzi:


1068

W przypadku nowszej wersji Node.js (wersja 8.1.4) zdarzenia i wywołania są podobne lub identyczne jak w starszych wersjach, ale zaleca się korzystanie ze standardowych nowszych funkcji językowych. Przykłady:

W przypadku buforowanych, nie sformatowanych danych wyjściowych (otrzymujesz wszystko naraz), użyj 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}`);
});

Możesz go również używać z Obietnicami:

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

Jeśli chcesz otrzymywać dane stopniowo we fragmentach (dane wyjściowe jako strumień), użyj 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}`);
});

Obie te funkcje mają synchroniczny odpowiednik. Przykład dla 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');

Jak również 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);

Uwaga: Poniższy kod jest nadal funkcjonalny, ale przede wszystkim jest przeznaczony dla użytkowników ES5 i wcześniejszych.

Moduł do odradzania procesów potomnych za pomocą Node.js jest dobrze udokumentowany w dokumentacji (v5.0.0). Aby wykonać polecenie i pobrać pełne dane wyjściowe jako bufor, użyj 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
});

Jeśli chcesz obsługiwać procesowe operacje we / wy ze strumieniami, na przykład gdy oczekujesz dużej ilości danych wyjściowych, użyj 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);

Jeśli wykonujesz plik zamiast polecenia, możesz użyć tego child_process.execFile, które parametry są prawie identyczne spawn, ale mają czwarty parametr wywołania zwrotnego, taki jak execdo pobierania buforów wyjściowych. Może to wyglądać trochę tak:

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

Od v0.11.12 Node obsługuje teraz synchroniczne spawni exec. Wszystkie metody opisane powyżej są asynchroniczne i mają synchroniczny odpowiednik. Dokumentację dla nich można znaleźć tutaj . Chociaż są one przydatne w skryptach, zauważ, że w przeciwieństwie do metod używanych do asynchronicznego odradzania procesów potomnych, metody synchroniczne nie zwracają instancji ChildProcess.


19
DZIĘKUJĘ CI. To doprowadzało mnie do szału. Czasami pomaga po prostu wskazać oczywiste rozwiązanie, dzięki czemu my noobowie (do węzła) mogą się uczyć i działać z nim.
Dave Thompson,

10
Uwaga: wymagany („child_process”). ExecFile () będzie interesujący dla osób, które muszą uruchomić plik, a nie dla całego systemu znanego polecenia, takiego jak książę.
Louis Ameline

2
Zamiast child.pipe(dest)(który nie istnieje), trzeba użyć child.stdout.pipe(dest)i child.stderr.pipe(dest), na przykład child.stdout.pipe(process.stdout)a child.stderr.pipe(process.stderr).
ComFreek

Co jeśli nie chcę umieszczać wszystkiego w pliku, ale chcę wykonać więcej niż jedno polecenie? Może jak echo "hello"i echo "world".
Cameron

czy to standardowy sposób na zrobienie tego? mam na myśli, jak wszystkie opakowania są napisane w nodejs? mam na myśli, powiedzmy, gearman, rabbitmq itp., które wymagają uruchomienia polecenia, ale mają także trochę otoki, ale nie mogę znaleźć żadnego z tych kodów w kodzie biblioteki
ANinJa

261

Węzeł JS v13.9.0, LTS v12.16.1i v10.19.0 --- marzec 2020

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


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


Synchronizacja:

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

Z dokumentacji Node.js v13.9.0

To samo dotyczy Dokumentacji Node.js v12.16.1 i Dokumentacji Node.js 10.19.0


8
Dziękujemy za podanie odpowiednich i prostych wersji. Nieco prostsza wersja synchronizacji była całkowicie w porządku dla mojego jednorazowego skryptu „zrób coś i wyrzuć to”, którego potrzebowałem.
Brian Jorden

Nie ma problemu! Zawsze miło jest mieć oba, nawet jeśli według niektórych nie jest to „właściwe”.
iSkore,

7
Warto zauważyć, że aby wykonać ten przykład w systemie Windows, należy użyć 'cmd', ['/c', 'dir']. Przynajmniej szukałem wysoko i nisko, dlaczego 'dir'bez argumentów nie działa, zanim to sobie przypomniałem ...;)
AndyO

1
Żadne z tych danych nie wysyłają NIC do konsoli.
Tyguy7

@ Tyguy7 jak go uruchomisz? Czy masz jakieś przesłonięcia obiektu konsoli?
iSkore,

73

Szukasz child_process.exec

Oto przykład:

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

To jest poprawne. Należy jednak pamiętać, że ten rodzaj wywoływania procesu potomnego ma ograniczenia dotyczące długości standardowego wyjścia.
hgoebl

@hgoebl, jaka jest zatem alternatywa?
Harshdeep,

2
@Harshdeep w przypadku długich wyjść standardowego wyjścia (kilka MB np.) Można słuchać datazdarzeń na standardowym wyjściu. Zajrzyj do dokumentacji, ale to musi być coś w rodzaju childProc.stdout.on("data", fn).
hgoebl

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

14
Dodaj więcej wyjaśnień dotyczących tego, jak działa ten kod i jak rozwiązuje odpowiedź. Pamiętaj, że StackOverflow tworzy archiwum odpowiedzi dla osób czytających to w przyszłości.
Al Sweigart,

4
To, co powiedział Al, jest prawdą, ale powiem, że zaletą tej odpowiedzi jest to, że jest ona o wiele prostsza niż konieczność przeczytania pierwszej odpowiedzi dla kogoś, kto potrzebuje szybkiej odpowiedzi.

29

Od wersji 4 najbliższą alternatywą jest child_process.execSyncmetoda:

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

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

Zauważ, że execSyncpętla zdarzeń blokuje wywołanie.


Działa to świetnie na najnowszym węźle. Czy child_processistota jest tworzona podczas używania execSync? I czy jest usuwany zaraz po wydaniu polecenia, prawda? Więc nie ma wycieków pamięci?
NiCk Newman

1
Tak, nie ma wycieków pamięci. Wydaje mi się, że inicjuje tylko struktury potomne procesów libuv bez tworzenia go w węźle.
Paul Rumkin,

21

Jeśli chcesz czegoś, co bardzo przypomina najwyższą odpowiedź, ale jest również synchroniczne, to zadziała.

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

var options = {
  encoding: 'utf8'
};

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

14

Właśnie napisałem pomocnika Cli do łatwego radzenia sobie z Uniksem / oknami.

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

Oryginalny plik źródłowy maszynopisu:

 /**
 * 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
Najnowsza wersja tam, z przykładem użycia Grunta w CLI: gist.github.com/Vadorequest/f72fa1c152ec55357839
Vadorequest

7

Teraz możesz używać shelljs (z węzła v4) w następujący sposób:

var shell = require('shelljs');

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

6

Jeśli nie masz nic przeciwko zależności i chcesz korzystać z obietnic, child-process-promisedziała:

instalacja

npm install child-process-promise --save

wykonanie Wykorzystanie

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

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

4

Użyj tego lekkiego npmpakietu:system-commands

Spójrz na to tutaj .

Zaimportuj to w ten sposób:

const system = require('system-commands')

Uruchom następujące polecenia:

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

Doskonały! Działa świetnie dla moich potrzeb.
roosevelt

3

Odpowiedź @ hexacyanide jest prawie kompletna. W Windows komenda princemoże być prince.exe, prince.cmd, prince.batlub po prostu prince(nie jestem świadomy tego, jak kamienie są powiązane, ale KMP Pojemniki wyposażone są w skrypcie sh i skryptu wsadowego - npminpm.cmd ). Jeśli chcesz napisać przenośny skrypt, który działałby w systemach Unix i Windows, musisz odrodzić odpowiedni plik wykonywalny.

Oto prosta, ale przenośna funkcja spawnowania:

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;
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.