1

I have developed a module to execute a generic command using the child_process spawn. I have two api in this module a single command execution api, CommandLine.execute and a multiple commands execution api, CommandLine.executeCommands, that I call in this way:

// commandItemsArray is a list of commands list
// that is an array of [command, options, arguments]
  commandItemsArray = [ ['ls','-l','./'], ['ls','-a','./'] ];
  command.executeCommands( commandItemsArray
    , function(results) {
      console.log( results );
    }
    , function(error) {
      console.log( error );
    });

or

commandItemsArray = [ ['ls','-l','./'] ];
    command.executeCommands( commandItemsArray
      , function(results) {
        console.log( results );
      }
      , function(error) {
        console.log( error );
      });

This module is standalone (relies only on node built-in modules) and that's the code:

(function() {

  var CommandLine;
  CommandLine = (function() {

    var cp = require('child_process');

    /**
     * Command line helper module
     * This module is standalone
     * @author: loretoparisi at gmail dot com
     */
    function CommandLine(options,logger) {
      var self=this;

      // defaults
      this._options = {
        // true to debug
        debug : false,
        // true to show errors
        error : true
      };

      // override defaults
      for (var attrname in options) { this._options[attrname] = options[attrname]; }

      /**
       * Wrappar around Promise.all
       */
      this.PromiseAll = function(items, block, done, fail) {
          var self = this;
          var promises = [],
              index = 0;
          items.forEach(function(item) {
              promises.push(function(item, i) {
                  return new Promise(function(resolve, reject) {
                      if (block) {
                          block.apply(this, [item, index, resolve, reject]);
                      }
                  });
              }(item, ++index))
          });
          Promise.all(promises).then(function AcceptHandler(results) {
              if (done) done(results);
          }, function ErrorHandler(error) {
              if (fail) fail(error);
          });
      }; //PromiseAll

    }

    /**
     * Execute a command
     * @param commands Array of command, options, arguments
     * @example ['ls','-l','./']
     * @param resolve Block success block
     * @param reject Block rejection block
     */
    CommandLine.prototype.execute = function(command, resolve, reject) {
      var self=this;
      resolve = resolve || function(results) {};
      reject = reject || function(error) {};
      return self.ExecutionBlock(item, index, resolve, reject);
    } //execute

    /**
     * Execute a list of commands
     * @param commands Array of command array of of command, options, arguments
     * @example [ ['ls','-l','./'], ['ls', '-a', './'] ]
     * @param resolve Block success block
     * @param reject Block rejection block
     */
    CommandLine.prototype.executeCommands = function(commands, resolve, reject) {
      var self=this;
      resolve = resolve || function(results) {};
      reject = reject || function(error) {};

      /**
       * Execution block handler
       */
      var ExecutionBlock = function(item, index, resolve, reject) {

        var executable = item[0]; // first elem is command
        var options = item.splice(1,item.length);

        if(self._options.debug) {
          console.log( item );
          console.log( executable, options.join(' ') );
        }

        var data = new Buffer("",'utf-8');
        // LP: now spawn the power!
        var child = cp.spawn(executable, options);
        // Listen for an exit event:
        child.on('exit', function(exitCode) {
          return resolve( { data : data.toString('utf-8'), code : exitCode } );
        });
        // Listen for stdout data
        child.stdout.on('data', function(_data) {
            console.log( ( new Buffer(_data)).toString() );
            data = Buffer.concat([data, _data]);
        });
        // child error
        child.stderr.on('data',
            function(data) {
              if(self._options.error) {
                console.log('err data: ' + data);
              }
              // on error, kill this child
              child.kill();
              return reject(new Error(data.toString()));
            }
        );

      } //ExecutionBlock

      self.PromiseAll(commands
        , function(item, index, _resolve, _reject) {
          ExecutionBlock(item, index, _resolve, _reject);
        }
        , function(results) { // aggregated results
          // all execution done here. The process exitCodes will be returned
            // array index is the index of the processed that exited
            return resolve(results);
          }
        , function(error) { // error
            return reject(error);
        });

    } //executeCommands

    return CommandLine;

  })();

  module.exports = CommandLine;

}).call(this);

I'm using a Promise.all to spawn multiple processes, wait execution, and collect stdout output in a node Buffer like:

    // Listen for stdout data
    child.stdout.on('data', function(_data) {
        console.log( ( new Buffer(_data)).toString() );
        data = Buffer.concat([data, _data]);
    });

At the very exit child event I return the child process exit code and data in a struct:

    child.on('exit', function(exitCode) {
      return resolve( { data : data.toString('utf-8'), code : exitCode } );
    });

While, executing some commands has no problem, executing shell commands like ls give me back unexpected results such as I do not get the results expected to be in the callback of PromiseAll method, and I do not understand why.

self.PromiseAll(commands
        , function(item, index, _resolve, _reject) {
          ExecutionBlock(item, index, _resolve, _reject);
        }
        , function(results) { // aggregated results
          // all execution done here. The process exitCodes will be returned
            // array index is the index of the processed that exited
            return resolve(results);
          }
        , function(error) { // error
            return reject(error);
        }); 

One doubt is about the node Buffer concatenation explained here, that seems to be tricky:

    var data = new Buffer("",'utf-8');
    // LP: now spawn the power!
    var child = cp.spawn(executable, options);
    // Listen for an exit event:
    child.on('exit', function(exitCode) {
      return resolve( { data : data.toString('utf-8'), code : exitCode } );
    });
    // Listen for stdout data
    child.stdout.on('data', function(_data) {
        console.log( ( new Buffer(_data)).toString() );
        data = Buffer.concat([data, _data]);
    });

I will expect { data : data.toString('utf-8')} to keep the whole buffer chained as string, but I wonder if the utf-8 is the right encoding for the stdout output, supposed that we are talking about of shell commands of course.

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.