116

我仍在尝试掌握如何运行 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 的缓冲区限制。

最终决议

我接受詹姆斯怀特的回答,但这是对我有用的确切代码......

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

10 回答 10

90

这里有三个问题需要解决:

首先是您在异步使用 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) }
);
于 2013-01-24T21:06:59.487 回答
79

接受答案的简化版本(第三点)对我有用。

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) });
于 2013-07-04T09:16:29.287 回答
52

我更简洁地使用了这个:

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

它完美地工作。:)

于 2014-04-18T14:40:29.410 回答
45

最简单的方法是只使用 ShellJS lib ...

$ npm install [-g] shelljs

执行示例:

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 命令,包括:

  • 光盘
  • 修改
  • cp
  • 目录
  • 回声
  • 执行
  • 出口
  • 寻找
  • grep
  • ln
  • ls
  • mkdir
  • MV
  • 流行音乐
  • 密码
  • R M
  • sed
  • 测试
  • 哪一个
于 2014-07-16T09:28:01.467 回答
4

我有一个类似的问题,我最终为此编写了一个节点扩展。您可以查看 git 存储库。它是开源的,免费的,所有的好东西!

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

ExecXI是一个用C++编写的节点扩展,用于逐条执行shell命令,将命令的输出实时输出到控制台。存在可选的链式和非链式方式;这意味着您可以选择在命令失败(链接)后停止脚本,或者您可以继续执行,就好像什么都没发生一样!

使用说明在自述文件中。随时提出拉取请求或提交问题!

我认为值得一提。

于 2013-02-28T07:31:06.183 回答
4

@TonyO'Hagan 是全面的shelljs答案,但是,我想强调他的答案的同步版本:

var shell = require('shelljs');
var output = shell.exec('netstat -rn', {silent:true}).output;
console.log(output);
于 2016-09-23T00:41:02.883 回答
2

同步单线:

require('child_process').execSync("echo 'hi'", function puts(error, stdout, stderr) {
  console.log(stdout) 
});
于 2017-01-27T19:34:19.780 回答
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.

于 2013-01-22T13:03:45.443 回答
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...'

工作得很好。:)

于 2013-01-25T03:11:37.470 回答
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))
于 2018-02-10T06:17:59.390 回答