Search code examples
node.jschild-processspawn

Use spawnSync to stream output from child process but save the output to parent variable


I want to be able to display a commands output while it is running in child process and then process the output in my program. Is this possible?

var result = spawnSync('neo', ['-help'], {
    stdio: 'inherit',
    encoding: 'utf-8'
});
var savedOutput = result.stdout;

console.log(String(savedOutput));

Solution

  • So here is what I ended up doing.

    I wrapped a great utility cross-spawn into my own module.

    exec.js

    var spawn = require('cross-spawn');
    var util = require('util');
    const Sugar = require('sugar');
    
    // parses the output if it is in JSON format
    function parse(str){
        var outStr = str, res;
        try {
            res = JSON.parse(outStr.trim());
        } catch (error) {
            res = outStr;
        }
    
        return res;
    }
    
    module.exports = {
        spawn() {
            var args = Array.prototype.slice.call(arguments);
            return new Promise(function (resolve, reject) {
                var stdout = '', stderr = '';
                var cp = spawn.apply(null, args);
                cp.stdout.on('data', function (chunk) {
                    var res = parse(chunk.toString());
                    if (!res.result && res.commandOutput) { // pretty log
                        console.log(util.inspect(Sugar.Object.filter(res, (val, key) => key !== 'commandOutput'),{colors: true, depth:null}));
                        console.log(res.commandOutput);
                    } else console.log(res);
    
                    stdout += chunk;
                });
                cp.stderr.on('data', function (chunk) {
                    console.error(chunk.toString());
                    stderr += chunk;
                });
                cp.on('error', (e)=>{
                    reject((stderr && new Error(stderr)) || e);
                })
                    .on('close', function (code) {
                        let output = parse(stdout); 
                        if (code === 0) {
                            resolve(output);
                        } else {
                            let err = (output && output.errorMsg && new Error(output.errorMsg)) || new Error(stderr);
                            console.error(err);
                            reject(err);
                        }
                    });
            });
        },
    };

    and then I just use it as any regular Promise shell exec:

    const exec = require('./lib/exec'); ... function execAndLog (){ ... return exec.spawn(...execArgs, opts).then(processOutput); }