eksekusi perintah shell node.js

113

Saya masih mencoba untuk memahami poin-poin penting tentang bagaimana saya dapat menjalankan perintah shell linux atau windows dan menangkap keluaran dalam node.js; akhirnya, saya ingin melakukan sesuatu seperti ini ...

//pseudocode
output = run_command(cmd, args)

Bagian penting adalah yang outputharus tersedia untuk variabel (atau objek) dengan cakupan global. Saya mencoba fungsi berikut, tetapi untuk beberapa alasan, saya undefineddicetak ke konsol ...

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" <------

Saya mengalami kesulitan memahami di mana kode rusak di atas ... prototipe yang sangat sederhana dari model itu berfungsi ...

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" <----

Dapatkah seseorang membantu saya memahami mengapa try_this()berhasil, dan run_cmd()tidak? FWIW, saya perlu menggunakan child_process.spawn, karena child_process.execmemiliki batas buffer 200KB.

Resolusi Akhir

Saya menerima jawaban James White, tetapi ini adalah kode persis yang berhasil untuk saya ...

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);
Mike Pennington
sumber
2
Dalam Resolusi Akhir Anda harus menetapkan me.stdout = "";dalam cmd_exec()untuk mencegah concatenating undefinedke awal hasilnya.
aorcsik
Hei, kode resolusi akhir itu benar-benar buruk, bagaimana jika butuh lebih dari 0,25 detik untuk mengeksekusi netstat Anda?
Steven Lu
Ummmm ... mungkin menggunakan salah satu jawaban yang saya berikan bonus ?????
Mike Pennington
Kemungkinan duplikat dari Execute dan dapatkan output dari perintah shell di node.js
Damjan Pavlica

Jawaban:

89

Ada tiga masalah di sini yang perlu diperbaiki:

Pertama , Anda mengharapkan perilaku sinkron saat menggunakan stdout secara asinkron. Semua panggilan dalam run_cmdfungsi Anda tidak sinkron, sehingga akan memunculkan proses turunan dan segera kembali terlepas dari apakah beberapa, semua, atau tidak ada data yang telah dibaca dari stdout. Dengan demikian, saat Anda berlari

console.log(foo.stdout);

Anda mendapatkan apa pun yang terjadi untuk disimpan di foo.stdout saat ini, dan tidak ada jaminan apa yang akan terjadi karena proses anak Anda mungkin masih berjalan.

Kedua adalah bahwa stdout adalah aliran yang dapat dibaca , jadi 1) peristiwa data dapat dipanggil beberapa kali, dan 2) panggilan balik diberikan buffer, bukan string. Mudah untuk diperbaiki; ganti saja

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

ke

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

sehingga kami mengubah buffer kami menjadi string dan menambahkan string itu ke variabel stdout kami.

Ketiga , Anda hanya dapat mengetahui bahwa Anda telah menerima semua keluaran saat mendapatkan peristiwa 'end', yang berarti kita memerlukan listener dan callback lain:

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

Jadi, hasil akhir Anda adalah ini:

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) }
);
James White
sumber
"tidak ada jaminan apa yang akan terjadi karena proses anak Anda mungkin masih berjalan" tutup ... tetapi ada jaminan bahwa ini tidak akan disetel pada saat itu, dan hanya akan disetel saat callback akhirnya dipanggil, seperti yang Anda sebutkan di tempat lain
4
Ini adalah jawaban yang sangat bagus dengan penjelasan yang bagus tentang beberapa konsep JS yang sangat penting. Bagus!
L0j1k
1
Anda akan ingin melakukan this.stdout = "";di dalam run()fungsi, jika tidak, Anda console.log(foo.sdtout);akan diawali dengan undefined.
f1lt3r
78

Versi sederhana dari jawaban yang diterima (poin ketiga), berhasil untuk saya.

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

Pemakaian:

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

run_cmd( "hostname", [], function(text) { console.log (text) });
cibercitizen 1
sumber
1
Bagaimana dengan nilai kembali, ketika proses mengembalikan bukan nol, bagaimana saya bisa mendapatkannya?
Vitim.us
2
child.stdout.on('close', (errCode) => { console.log(errCode) } )
Tushar Gautam
52

Saya menggunakan ini lebih singkat:

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

itu bekerja dengan sempurna. :)

Mimouni
sumber
1
Ini berfungsi dengan baik, dan tidak memerlukan modul node tambahan apa pun. Saya suka itu!
bearvarine
9
sys.puts()tidak digunakan lagi pada tahun 2011 (dengan Node.js v0.2.3). Anda harus menggunakan console.log()sebagai gantinya.
tfmontague
1
yang benar-benar berfungsi ... jadi saya bertanya-tanya, mengapa ini bukan jawabannya? ini sangat sederhana
ekkis
Wow!! jawaban ini sempurna dan elegan. Terima kasih.
Em Ji Madhu
43

Cara termudah adalah dengan hanya menggunakan ShellJS lib ...

$ npm install [-g] shelljs

EXEC Contoh:

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 mendukung banyak perintah shell umum yang dipetakan sebagai fungsi NodeJS termasuk:

  • kucing
  • CD
  • chmod.dll
  • cp
  • dirs
  • gema
  • eksekutif
  • keluar
  • Temukan
  • grep
  • ln
  • ls
  • mkdir
  • mv
  • popd
  • pushd
  • pwd
  • rm
  • sed
  • uji
  • yang
Tony O'Hagan
sumber
bagaimana cara menambahkan parameter ke skrip shell yang dipanggil oleh shell.exec ("foo.sh")?
pseudozach
1
Anda hanya dapat menambahkan argumen Anda atas string: shell.exec("foo.sh arg1 arg2 ... "). foo.shSkrip Anda dapat mereferensikan ini menggunakan $1, $2... dll.
Tony O'Hagan
Jika perintah yang Anda coba jalankan akan membutuhkan masukan dari pengguna maka JANGAN gunakan ShellJS exec (). Fungsi ini tidak bersifat interaktif, karena hanya akan mengambil perintah dan mencetak keluaran, Tidak dapat menerima masukan di antaranya. Gunakan built in child_process sebagai gantinya. Misalnya https://stackoverflow.com/a/31104898/9749509
MPatel1
4

Saya memiliki masalah yang sama dan saya akhirnya menulis ekstensi node untuk ini. Anda dapat melihat repositori git. Ini open source dan gratis dan semua hal bagus itu!

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

ExecXI adalah ekstensi node yang ditulis dalam C ++ untuk menjalankan perintah shell satu per satu, mengeluarkan output perintah ke konsol secara real-time. Ada cara opsional dirantai, dan tidak dirantai; artinya Anda dapat memilih untuk menghentikan skrip setelah perintah gagal (dirantai), atau Anda dapat melanjutkan seolah-olah tidak ada yang terjadi!

Petunjuk penggunaan ada di file ReadMe . Jangan ragu untuk membuat permintaan penarikan atau mengirimkan masalah!

Saya pikir itu layak untuk disebutkan.

Logan
sumber
4

@ TonyO'Hagan adalah shelljsjawaban yang komprehensif , tetapi, saya ingin menyoroti versi sinkron dari jawabannya:

var shell = require('shelljs');
var output = shell.exec('netstat -rn', {silent:true}).output;
console.log(output);
Stephen Quan
sumber
1

Satu baris sinkron:

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

Victorio Berra
sumber
0

Ada konflik variabel dalam run_cmdfungsi Anda :

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

Cukup ubah menjadi ini:

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

Untuk melihat kesalahan selalu tambahkan ini:

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

EDIT Kode Anda gagal karena Anda mencoba menjalankan diryang tidak disediakan sebagai program mandiri terpisah. Ini adalah perintah dalam cmdproses. Jika Anda ingin bermain dengan filesystem gunakan native require( 'fs' ).

Atau (yang tidak saya rekomendasikan) Anda dapat membuat file batch yang kemudian dapat Anda jalankan. Perhatikan bahwa OS secara default mengaktifkan file batch melalui cmd.

aneh
sumber
terima kasih atas bantuan Anda ... namun, bahkan ketika saya menjalankan C:\Windows\System32\netstat.exe, ini masih tidak membuahkan hasil ... sintaks yang tepat saya adalah foo = new run_cmd('netstat.exe', ['-an'], function (me, data){me.stdout=data;});... Saya juga mencoba jalur lengkap tanpa hasil sejauh ini
Mike Pennington
0

Anda sebenarnya tidak mengembalikan apa pun dari fungsi run_cmd Anda.

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...'

Bekerja dengan baik. :)

Chris Eineke
sumber
Saya tidak berpikir returndiperlukan selama Anda mengatur atribut objek dengan benar
Mike Pennington
0

Versi yang dijanjikan dari jawaban paling banyak diberikan:

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

Menggunakan:

 runCmd('ls').then(ret => console.log(ret))
jujur
sumber