выполнение команды оболочки node.js

113

Я все еще пытаюсь понять тонкости того, как я могу запустить команду оболочки Linux или Windows и записать вывод в node.js; в конце концов, я хочу сделать что-то подобное ...

//pseudocode
output = run_command(cmd, args)

Важная часть - это то, что outputдолжно быть доступно для переменной (или объекта) с глобальной областью видимости. Я попробовал следующую функцию, но по какой-то причине меня undefinedвыводят на консоль ...

function run_cmd(cmd, args, cb) {
  var spawn = require('child_process').spawn
  var child = spawn(cmd, args);
  var me = this;
  child.stdout.on('data', function(me, data) {
    cb(me, data);
  });
}
foo = new run_cmd('dir', ['/B'], function (me, data){me.stdout=data;});
console.log(foo.stdout);  // yields "undefined" <------

Мне сложно понять, где приведенный выше код ломается ... работает очень простой прототип этой модели ...

function try_this(cmd, cb) {
  var me = this;
  cb(me, cmd)
}
bar = new try_this('guacamole', function (me, cmd){me.output=cmd;})
console.log(bar.output); // yields "guacamole" <----

Может кто-нибудь помочь мне понять, почему try_this()работает, а run_cmd()не нет? FWIW, мне нужно использовать child_process.spawn, потому что у child_process.execнего ограничение буфера 200 КБ .

Окончательное разрешение

Я принимаю ответ Джеймса Уайта, но это точный код, который сработал для меня ...

function cmd_exec(cmd, args, cb_stdout, cb_end) {
  var spawn = require('child_process').spawn,
    child = spawn(cmd, args),
    me = this;
  me.exit = 0;  // Send a cb to set 1 when cmd exits
  me.stdout = "";
  child.stdout.on('data', function (data) { cb_stdout(me, data) });
  child.stdout.on('end', function () { cb_end(me) });
}
foo = new cmd_exec('netstat', ['-rn'], 
  function (me, data) {me.stdout += data.toString();},
  function (me) {me.exit = 1;}
);
function log_console() {
  console.log(foo.stdout);
}
setTimeout(
  // wait 0.25 seconds and print the output
  log_console,
250);
Майк Пеннингтон
источник
2
В итоговой резолюции следует установить me.stdout = "";в cmd_exec()предотвратить конкатенации undefinedк началу результата.
aorcsik
Эй, этот код окончательного разрешения совершенно ужасен, что, если для выполнения вашего netstat требуется больше 0,25 секунды?
Стивен Лу
Умммм ... может быть, воспользуйтесь одним из ответов, на который я наградил бонусом ?????
Майк Пеннингтон

Ответы:

89

Здесь есть три проблемы, которые необходимо исправить:

Во-первых , вы ожидаете синхронного поведения при асинхронном использовании stdout. Все вызовы в вашей run_cmdфункции являются асинхронными, поэтому он порождает дочерний процесс и немедленно возвращается, независимо от того, были ли некоторые, все или никакие данные считаны из стандартного вывода. Таким образом, когда вы бежите

console.log(foo.stdout);

вы получаете все, что в данный момент хранится в foo.stdout, и нет никакой гарантии, что это будет, потому что ваш дочерний процесс может все еще работать.

Во-вторых , stdout является читаемым потоком , поэтому 1) событие данных может вызываться несколько раз, и 2) обратному вызову предоставляется буфер, а не строка. Легко исправить; просто измени

foo = new run_cmd(
    'netstat.exe', ['-an'], function (me, data){me.stdout=data;}
);

в

foo = new run_cmd(
    'netstat.exe', ['-an'], function (me, buffer){me.stdout+=buffer.toString();}
);

чтобы мы преобразовали наш буфер в строку и добавили эту строку к нашей переменной stdout.

В-третьих , вы можете узнать, что получили весь вывод, только когда получите событие 'end', что означает, что нам нужен другой слушатель и обратный вызов:

function run_cmd(cmd, args, cb, end) {
    // ...
    child.stdout.on('end', end);
}

Итак, ваш окончательный результат таков:

function run_cmd(cmd, args, cb, end) {
    var spawn = require('child_process').spawn,
        child = spawn(cmd, args),
        me = this;
    child.stdout.on('data', function (buffer) { cb(me, buffer) });
    child.stdout.on('end', end);
}

// Run C:\Windows\System32\netstat.exe -an
var foo = new run_cmd(
    'netstat.exe', ['-an'],
    function (me, buffer) { me.stdout += buffer.toString() },
    function () { console.log(foo.stdout) }
);
Джеймс Уайт
источник
"нет никакой гарантии, что это будет, потому что ваш дочерний процесс все еще может работать" близко ... но есть гарантия, что он не будет установлен в этот момент времени, и будет установлен только тогда, когда обратный вызов будет окончательно вызван, как вы указали в другом месте
4
Это отличный ответ с подробным объяснением некоторых очень важных концепций JS. Ницца!
L0j1k
1
Вы захотите сделать это this.stdout = "";внутри run()функции, иначе ваш console.log(foo.sdtout);будет иметь префикс undefined.
f1lt3r 07
78

Упрощенная версия принятого ответа (третий пункт) просто сработала для меня.

function run_cmd(cmd, args, callBack ) {
    var spawn = require('child_process').spawn;
    var child = spawn(cmd, args);
    var resp = "";

    child.stdout.on('data', function (buffer) { resp += buffer.toString() });
    child.stdout.on('end', function() { callBack (resp) });
} // ()

Использование:

run_cmd( "ls", ["-l"], function(text) { console.log (text) });

run_cmd( "hostname", [], function(text) { console.log (text) });
cibercitizen1
источник
1
Как насчет возвращаемого значения, когда процесс возвращает ненулевое значение, как я могу его получить?
Vitim.us
2
child.stdout.on('close', (errCode) => { console.log(errCode) } )
Tushar Gautam
52

Я использовал это более кратко:

var sys = require('sys')
var exec = require('child_process').exec;
function puts(error, stdout, stderr) { sys.puts(stdout) }
exec("ls -la", puts);

работает отлично. :)

Мимуни
источник
1
Это хорошо работает и не требует дополнительных узловых модулей. Мне это нравится!
Bearvarine
9
sys.puts()устарела в 2011 году (с Node.js v0.2.3). console.log()Вместо этого вы должны использовать .
tfmontague 05
1
это действительно работает ... поэтому мне интересно, почему это не ответ? это так просто
ekkis 01
Вот это да!! этот ответ идеален и элегантен. Спасибо.
Эм Джи Мадху
43

Самый простой способ - просто использовать ShellJS lib ...

$ npm install [-g] shelljs

Пример EXEC:

require('shelljs/global');

// Sync call to exec()
var version = exec('node --version', {silent:true}).output;

// Async call to exec()
exec('netstat.exe -an', function(status, output) {
  console.log('Exit status:', status);
  console.log('Program output:', output);
});

ShellJs.org поддерживает множество распространенных команд оболочки, отображаемых как функции NodeJS, включая:

  • кошка
  • CD
  • chmod
  • ср
  • каталоги
  • эхо
  • Exec
  • выход
  • найти
  • Grep
  • пер
  • ls
  • mkdir
  • милливольт
  • POPD
  • Pushd
  • PWD
  • комната
  • СЭД
  • тест
  • который
Тони О'Хаган
источник
как добавить параметр в сценарий оболочки, вызываемый shell.exec ("foo.sh")?
псевдозач 06
1
Вы можете просто добавить ваши аргументы вверх строку: shell.exec("foo.sh arg1 arg2 ... "). Ваш foo.shскрипт может ссылаться на них , используя $1, $2... и т.д.
Tony О'Хаган
Если команда, которую вы пытаетесь выполнить, потребует ввода от пользователя, НЕ используйте ShellJS exec (). Эта функция не является интерактивной по своей природе, так как она просто принимает команду и выводит на печать, не может принимать ввод между ними. Вместо этого используйте встроенный child_process. Например, https://stackoverflow.com/a/31104898/9749509
MPatel1
4

У меня была аналогичная проблема, и в итоге я написал для нее расширение узла. Вы можете проверить репозиторий git. Это открытый исходный код, бесплатный и все такое хорошее!

https://github.com/aponxi/npm-execxi

ExecXI - это расширение узла, написанное на C ++ для выполнения команд оболочки одну за другой, выводя вывод команды на консоль в режиме реального времени. Присутствуют необязательные цепные и несвязанные пути; Это означает, что вы можете остановить сценарий после сбоя команды (связать) или продолжить работу, как будто ничего не произошло!

Инструкции по использованию находятся в файле ReadMe . Не стесняйтесь делать запросы на вытягивание или отправлять вопросы!

Я подумал, что стоит упомянуть об этом.

Логан
источник
4

@ TonyO'Hagan - исчерпывающий shelljsответ, но я хотел бы выделить синхронную версию его ответа:

var shell = require('shelljs');
var output = shell.exec('netstat -rn', {silent:true}).output;
console.log(output);
Стивен Куан
источник
1

Синхронный однострочный:

require('child_process').execSync("echo 'hi'", function puts(error, stdout, stderr) { console.log(stdout) });

Викторио Берра
источник
0

В вашей run_cmdфункции есть конфликт переменных :

  var me = this;
  child.stdout.on('data', function(me, data) {
    // me is overriden by function argument
    cb(me, data);
  });

Просто измените его на это:

  var me = this;
  child.stdout.on('data', function(data) {
    // One argument only!
    cb(me, data);
  });

Чтобы увидеть ошибки, всегда добавляйте это:

  child.stderr.on('data', function(data) {
      console.log( data );
  });

ИЗМЕНИТЬ Ваш код не работает, потому что вы пытаетесь запустить, dirчто не предоставляется как отдельная автономная программа. Это команда в cmdпроцессе. Если вы хотите поиграть с файловой системой, используйте native require( 'fs' ).

В качестве альтернативы (что я не рекомендую) вы можете создать командный файл, который затем можно будет запустить. Обратите внимание, что ОС по умолчанию запускает командные файлы через cmd.

капризный
источник
спасибо за вашу помощь ... однако, даже когда я бегу C:\Windows\System32\netstat.exe, это все равно не дает результатов ... Мой точный синтаксис был foo = new run_cmd('netstat.exe', ['-an'], function (me, data){me.stdout=data;});... Я также пробовал полный путь, пока безуспешно
Майк Пеннингтон
0

Фактически вы ничего не возвращаете из своей функции run_cmd.

function run_cmd(cmd, args, done) {
    var spawn = require("child_process").spawn;
    var child = spawn(cmd, args);
    var result = { stdout: "" };
    child.stdout.on("data", function (data) {
            result.stdout += data;
    });
    child.stdout.on("end", function () {
            done();
    });
    return result;
}

> foo = run_cmd("ls", ["-al"], function () { console.log("done!"); });
{ stdout: '' }
done!
> foo.stdout
'total 28520...'

Работает нормально. :)

Крис Эйнеке
источник
Я не думаю, что returnэто требуется, если вы правильно устанавливаете атрибуты объекта
Майк Пеннингтон
0

Обещанный вариант ответа, получившего наибольшее количество наград:

  runCmd: (cmd, args) => {
    return new Promise((resolve, reject) => {
      var spawn = require('child_process').spawn
      var child = spawn(cmd, args)
      var resp = ''
      child.stdout.on('data', function (buffer) { resp += buffer.toString() })
      child.stdout.on('end', function () { resolve(resp) })
    })
  }

Использовать:

 runCmd('ls').then(ret => console.log(ret))
Фрэнк
источник