node.js shell命令执行


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 2015年

嘿,最终解决方案代码简直糟透了,如果执行netstat的时间超过0.25秒怎么办?
史蒂文·卢

嗯...也许使用我授予奖金的答案之一?????
Mike Pennington

Answers:


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 2014年

1
您需要this.stdout = "";run()函数内部进行操作,否则您的console.log(foo.sdtout);前缀为undefined
f1lt3r 2015年

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 2015年

2
child.stdout.on('close', (errCode) => { console.log(errCode) } )
Tushar Gautam'7

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

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函数的常见Shell命令,包括:

  • 光盘
  • chmod
  • cp
  • rs
  • 回声
  • 执行
  • 出口
  • grep
  • ln
  • ls
  • 麦克迪尔
  • MV
  • 弹出
  • 密码
  • R M
  • sed
  • 测试
  • 哪一个

如何在shell.exec(“ foo.sh”)调用的shell脚本中添加参数?
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 ++编写的节点扩展,用于一个接一个地执行shell命令,并将命令的输出实时输出到控制台。存在可选的链接和非链接方式;这意味着您可以选择在命令失败(链接)后停止脚本,也可以继续执行,好像什么都没发生!

使用说明位于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。如果要使用文件系统,请使用native 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))
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.