如何按顺序执行Shell命令?

5
我有一份希望用nodejs执行的shell命令列表:
// index.js
var commands = ["npm install", "echo 'hello'"];

var exec = require('child_process').exec;

for (var i = 0; i < commands.length; i++) {
    exec(commands[i], function(err, stdout) {
        console.log(stdout);
    });
}

当我运行这个命令时,命令是以相反的顺序执行的。为什么会这样?如何按顺序执行命令?
更好的方法是,有没有一种方法可以不使用nodejs来执行shell命令?我发现它对shell的异步处理有点麻烦。
注意:
我知道像shelljs这样的库存在。我只想使用基本的nodejs来完成这个任务。

由于exec是异步方法,因此echo命令执行速度更快。这是一个提示:您需要重新考虑如何设置执行。 - Dave Newton
它们不是按相反的顺序执行的,它们是异步的,一个不等待另一个。Shell 命令由 Shell 执行,而 exec 只是一个访问它的接口。您可以在终端中运行任何这些命令。 - Yerken
JavaScript中的异步for循环 - Jonathan Lonowski
2个回答

14

因为 exec() 是非阻塞的,所以您的 for 循环会同时执行所有异步操作,它们完成的顺序取决于它们的执行时间,并且是不确定的。如果您确实希望它们按顺序执行,那么您需要执行一个操作,等待它调用其完成回调,然后再执行下一个操作。

在JavaScript中,您不能使用传统的 for 循环来“等待”异步操作完成以便按顺序执行它们。相反,您必须手动迭代,其中您在前一个操作的完成回调中启动下一次迭代。我通常使用计数器和名为 next() 的本地函数来实现:

手动异步迭代

var commands = ["npm install", "echo 'hello'"];

var exec = require('child_process').exec;

function runCommands(array, callback) {

    var index = 0;
    var results = [];

    function next() {
       if (index < array.length) {
           exec(array[index++], function(err, stdout) {
               if (err) return callback(err);
               // do the next iteration
               results.push(stdout);
               next();
           });
       } else {
           // all done here
           callback(null, results);
       }
    }
    // start the first iteration
    next();
}

runCommands(commands, function(err, results) {
    // error or results here
});

ES6承诺

自从ES6将Promise标准化并内置于node.js中,我喜欢使用Promise来进行异步操作:

var exec = require('child_process').exec;

function execPromise = function(cmd) {
    return new Promise(function(resolve, reject) {
        exec(cmd, function(err, stdout) {
            if (err) return reject(err);
            resolve(stdout);
        });
    });
}

var commands = ["npm install", "echo 'hello'"];

commands.reduce(function(p, cmd) {
    return p.then(function(results) {
        return execPromise(cmd).then(function(stdout) {
            results.push(stdout);
            return results;
        });
    });
}, Promise.resolve([])).then(function(results) {
    // all done here, all results in the results array
}, function(err) {
    // error here
});

蓝鸟Promises

使用蓝鸟Promise库,这将更加简单:

var Promise = require('bluebird');
var execP = Promise.promisify(require('child_process').exec);

var commands = ["npm install", "echo 'hello'"];
Promise.mapSeries(commands, execP).then(function(results) {
    // all results here
}, function(err) {
    // error here
});

1

选择一:如果有“...Sync”版本的函数,请使用该版本

在这种情况下,已经存在一个execSync函数:

child_process.execSync(command[, options])

选择二:使用生成器的神奇力量!

对于更通用的情况,在现代环境中,您可以使用例如“生成器”模式从内部转换任何异步函数的执行方式,这对于任何顺序操作系统脚本非常有用。

以下是如何在Node.js v6+(我认为也适用于v4+)中以同步方式使用readline异步函数的示例:

var main = (function* () {
  var rl = require('readline')
          .createInterface({input: process.stdin, output: process.stdout });
  // the callback uses the iterator '.next()' to resume the 'yield'
  a = yield rl.question('do you want this? ', r=>main.next(r))  
  b = yield rl.question('are you sure? ', r=>main.next(r))      
  rl.close()
  console.log(a,b)
})()          // <- generator executed, iterator 'main' created
main.next()   // <- start iterator, run till the first 'yield'

一些示例会很好! - user1034912

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