Node.js shell命令执行

117

我仍在努力掌握如何在node.js中运行Linux或Windows shell命令并捕获输出的细节;最终,我想做类似这样的事情...

//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()不能吗?顺便说一下,我需要使用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
在最终解决方案中,您应该在cmd_exec()中设置me.stdout = "";以防止将undefined连接到结果的开头。 - aorcsik
嘿,那个最终解决方案的代码真是糟糕透了,如果执行你的netstat需要超过0.25秒,会怎么样? - Steven Lu
嗯...也许可以使用我给予奖励的答案之一吗? - Mike Pennington
可能是在Node.js中执行并获取shell命令的输出的重复问题。 - Damjan Pavlica
10个回答

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

“这并没有保证,因为您的子进程可能仍在运行。” 关闭...但可以保证此时该变量 不会 被设置,并且只有在回调函数最终被调用时才会被设置,正如您在其他地方所指示的那样。 - user748221
4
这是一个非常好的回答,对一些非常重要的 JavaScript 概念进行了很好的解释。不错! - L0j1k
1
你需要在 run() 函数内部添加 this.stdout = "";,否则你的 console.log(foo.stdout); 会带有 undefined 前缀。 - f1lt3r

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

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
10
sys.puts()在2011年已被废弃(与Node.js v0.2.3版本一起)。您应该改用console.log() - tim-montague
1
那真的能够运行...所以我在想,为什么这不是答案呢?它很简单。 - ekkis
哇!这个答案完美而优雅。谢谢。 - Em Ji Madhu

45

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

$ 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支持许多常见的shell命令,被映射为NodeJS函数,包括:

  • cat:显示文件内容
  • cd:更改当前工作目录
  • chmod:更改文件权限
  • cp:复制文件或目录
  • dirs:列出目录堆栈
  • echo:输出文本
  • exec:执行命令
  • exit:退出进程
  • find:查找文件
  • grep:搜索文件内容
  • ln:创建链接
  • ls:列出目录内容
  • mkdir:创建目录
  • mv:移动文件或目录
  • popd:从目录堆栈中弹出并更改当前目录
  • pushd:将目录压入堆栈并更改当前目录
  • pwd:显示当前工作目录
  • rm:删除文件或目录
  • sed:流编辑器
  • test:测试文件类型
  • which:查找命令的路径

如何向由shell.exec("foo.sh")调用的shell脚本添加参数? - pseudozach
1
你可以将参数附加到字符串中:shell.exec("foo.sh arg1 arg2 ... ")。你的 foo.sh 脚本可以使用 $1$2 等来引用它们。 - Tony O'Hagan
如果您要执行的命令需要用户输入,请勿使用ShellJS exec()。此函数不具有交互性质,它只会接收命令并输出结果,无法在中途接受输入。请改用内置的child_process。例如:https://dev59.com/-F0a5IYBdhLWcg3wmZvC#31104898 - MPatel1

4

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

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

4

我遇到过类似的问题,最终编写了一个 Node 扩展来解决。你可以查看 git 存储库。它是开源和免费的,所有好东西都在那里!

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

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

使用说明在ReadMe文件中。欢迎提交拉取请求或问题!

我认为值得一提。


2
同步一行代码:
require('child_process').execSync("echo 'hi'", function puts(error, stdout, stderr) {
  console.log(stdout) 
});

0

最受好评答案的 Promisified 版本:

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

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接