การดำเนินการคำสั่งเชลล์ node.js


113

ฉันยังคงพยายามเข้าใจประเด็นที่ละเอียดกว่าว่าฉันสามารถรันคำสั่ง linux หรือ windows shell และจับเอาท์พุทภายใน 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มีบัฟเฟอร์ จำกัด 200KB

ความละเอียดขั้นสุดท้าย

ฉันยอมรับคำตอบของ James White แต่นี่เป็นรหัสที่เหมาะกับฉัน ...

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

เฮ้รหัสความละเอียดขั้นสุดท้ายนั้นแย่มากถ้าใช้เวลานานกว่า 0.25 วินาทีในการเรียกใช้ netstat ของคุณล่ะ?
Steven Lu

อืม ... อาจจะใช้คำตอบที่ได้รับโบนัสไป ?????
Mike Pennington

คำตอบ:


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 ของเรา

ประการที่สามคือคุณสามารถรู้ได้ก็ต่อเมื่อคุณได้รับผลลัพธ์ทั้งหมดเมื่อคุณได้รับเหตุการณ์ 'สิ้นสุด' ซึ่งหมายความว่าเราต้องการผู้ฟังและการติดต่อกลับอื่น:

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

1
ที่ใช้งานได้จริง ... ก็เลยสงสัยว่าทำไมถึงไม่ได้คำตอบ? มันง่ายมาก
ekkis

ว้าว!! คำตอบนี้สมบูรณ์แบบและสวยงาม ขอบคุณ.
Em Ji Madhu

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 ได้แก่ :

  • แมว
  • ซีดี
  • chmod
  • ซีพี
  • dirs
  • เสียงสะท้อน
  • exec
  • ทางออก
  • หา
  • grep
  • LN
  • LS
  • mkdir
  • mv
  • popd
  • pushd
  • รหัสผ่าน
  • RM
  • sed
  • ทดสอบ
  • ที่

วิธีเพิ่มพารามิเตอร์ให้กับเชลล์สคริปต์ที่เรียกโดย shell.exec ("foo.sh")
pseudozach

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

แก้ไขโค้ดของคุณล้มเหลวเนื่องจากคุณกำลังพยายามเรียกใช้dirซึ่งไม่ได้จัดเตรียมไว้เป็นโปรแกรมแบบสแตนด์อโลนแยกต่างหาก มันเป็นคำสั่งในcmdกระบวนการ หากคุณต้องการเล่นกับระบบไฟล์ให้ใช้เนทีฟrequire( 'fs' )เนทีฟ

อีกทางเลือกหนึ่ง (ซึ่งฉันไม่แนะนำ) คุณสามารถสร้างไฟล์แบตช์ซึ่งคุณสามารถเรียกใช้ได้ โปรดทราบว่าโดยค่าเริ่มต้นระบบปฏิบัติการจะยิงไฟล์แบตช์ผ่านไฟล์cmd.


ขอบคุณสำหรับความช่วยเหลือของคุณ ... อย่างไรก็ตามแม้ว่าฉันจะเรียกใช้C:\Windows\System32\netstat.exeแต่ก็ยังไม่ได้ผลลัพธ์ ... ไวยากรณ์ที่แน่นอนของฉันคือfoo = new run_cmd('netstat.exe', ['-an'], function (me, data){me.stdout=data;});... ฉันยังลองเส้นทางแบบเต็ม แต่ก็ไม่ประสบความสำเร็จ
Mike Pennington

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จำเป็นตราบใดที่คุณตั้งค่าคุณสมบัติของวัตถุอย่างถูกต้อง
Mike Pennington

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))
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.