【问题标题】:node.js shell command executionnode.js shell 命令执行
【发布时间】:2013-01-05 16:19:59
【问题描述】:

我仍在尝试掌握如何运行 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);

【问题讨论】:

  • 在最终分辨率中,您应该在cmd_exec() 中设置me.stdout = "";,以防止将undefined 连接到结果的开头。
  • 嘿,最终解析代码太糟糕了,如果执行 netstat 的时间超过 0.25 秒怎么办?
  • Ummmm...也许使用我奖励给的答案之一??????

标签: javascript node.js shell


【解决方案1】:

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

首先是您在异步使用标准输出时期望同步行为。 run_cmd 函数中的所有调用都是异步的,因此无论是否从标准输出中读取部分、全部或没有数据,它都会生成子进程并立即返回。因此,当您运行时

console.log(foo.stdout);

你会得到任何发生在 foo.stdout 中的东西,并且不能保证会是什么,因为你的子进程可能仍在运行。

其次是stdout是一个readable stream,所以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) }
);

【讨论】:

  • "无法保证会是什么,因为您的子进程可能仍在运行" 关闭...但可以保证它不会在此时设置及时,并且只会在最终调用回调时设置,如您在其他地方指出的那样
  • 这是一个很好的答案,对一些非常重要的 JS 概念进行了很好的解释。不错!
  • 您需要在run() 函数中执行this.stdout = "";,否则您的console.log(foo.sdtout); 将以undefined 为前缀。
【解决方案2】:

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

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

【讨论】:

  • 返回值呢,当进程返回非零时,如何获取呢?
  • child.stdout.on('close', (errCode) =&gt; { console.log(errCode) } )
【解决方案3】:

我用这个更简洁:

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

完美运行。 :)

【讨论】:

  • 这很好用,不需要任何额外的节点模块。我喜欢!
  • sys.puts() 在 2011 年被弃用(使用 Node.js v0.2.3)。你应该改用console.log()
  • 确实有效...所以我想知道,为什么这不是答案?就是这么简单
  • 哇!!这个答案是完美而优雅的。谢谢。
【解决方案4】:

最简单的方法就是使用 ShellJS 库 ...

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

  • cd
  • chmod
  • cp
  • 目录
  • 回声
  • 执行
  • 退出
  • 查找
  • grep
  • ln
  • ls
  • mkdir
  • MV
  • popd
  • 推送
  • 密码
  • rm
  • sed
  • 测试
  • 哪个

【讨论】:

  • 如何在shell.exec("foo.sh")调用的shell脚本中添加参数?
  • 您可以将参数附加到字符串顶部:shell.exec("foo.sh arg1 arg2 ... ")。您的 foo.sh 脚本可以使用 $1$2 ... 等来引用这些内容。
  • 如果您尝试执行的命令需要用户输入,则不要使用 ShellJS exec()。这个函数本质上不是交互式的,因为它只会接受命令和打印输出,不能接受两者之间的输入。改用内置的 child_process 。例如。 https://*.com/a/31104898/9749509
【解决方案5】:

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

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

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

使用说明在ReadMe file 中。随意提出拉取请求或提交问题!

我觉得值得一提。

【讨论】:

    【解决方案6】:

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

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

    【讨论】:

      【解决方案7】:

      同步单线:

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

      【讨论】:

        【解决方案8】:

        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;});......我也尝试了完整的到目前为止没有成功的路径
        【解决方案9】:

        你实际上并没有从你的 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是必需的,只要你正确设置了对象属性
        【解决方案10】:

        获奖最多的答案的承诺版本:

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

        【讨论】: