Виконання команди оболонки 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функції є асинхронними, тому вона породить дочірній процес та повернеться негайно незалежно від того, чи були зчитані деякі, всі чи ні один із даних stdout. Як такий, коли ти біжиш

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

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

1
Як щодо поверненого значення, коли процес повертає не нуль, як я можу його отримати?
Vitim.us

2
child.stdout.on('close', (errCode) => { console.log(errCode) } )
Тушар Гаутам

52

Я використовував це більш стисло:

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

він працює чудово. :)


1
Це добре працює і не потребує додаткових модулів вузлів. Мені це подобається!
ведмедик

9
sys.puts()була застаріла в 2011 році (з Node.js v0.2.3). Ви повинні використовувати console.log()замість цього.
tfmontague

1
що насправді працює ... тому мені цікаво, чому це не відповідь? це так просто
ekkis

Ого!! ця відповідь ідеальна і елегантна. спасибі.
Ем Джі Мадху

43

Найпростіший спосіб - просто скористатися вкладкою ShellJS ...

$ 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
  • ср
  • панове
  • відлуння
  • виконувати
  • вихід
  • знайти
  • греп
  • ln
  • лс
  • mkdir
  • mv
  • попд
  • штовхач
  • pwd
  • рм
  • sed
  • тест
  • котрий

як додати параметр до сценарію оболонки, який називається shell.exec ("foo.sh")?
псевдозач

1
Ви можете просто додати ваші аргументи вгору рядок: shell.exec("foo.sh arg1 arg2 ... "). Ваш foo.shсценарій може посилатися на них, використовуючи $1, $2... тощо
Тоні О'Хаган

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


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

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