Koz Ross
Koz Ross

Reputation: 3140

Node.js - child_process.exec and output redirection

I'm trying to write a file templating script using Node.js. I have a JSON file called template.json which stores template information. The idea behind my script is that, if I type something like:

tmpl.js java Joe

it will do the following:

  1. Call touch Joe.java
  2. Read template.json to get the template for Java files
  3. Use its information to replace all the placeholders with Joe
  4. Write the result to Joe.java
  5. Execute emacsclient Joe.java

Now, I wrote this script as follows:

#!/usr/local/bin/node --harmony

var templates = require('./config/template.json'),
    args = process.argv;

if (args.length < 4) {
    console.log("Not enough arguments!");
} 
else {
    var type = args[2],
        name = args[3];
    if (type in templates) {
        var tmpl = templates[type],
            contents = make_output(tmpl["format"],name),
            file_name = name + tmpl["extension"],
            command = "touch " + file_name + " && echo -e '" + contents +
            "' &> " + file_name + " && emacsclient " + file_name;
        invoke(command);
    }
    else {
        console.log("No template for %s", type);
    }
}


//Helpers

//Invokes comm with args in the terminal, returns all output
//Does not play nice with command redirection
function invoke(comm) {
    var exec = require('child_process').exec,
    child = exec(comm,
             function (error, stdout, stderr) {
                         if (error !== null) {
                 console.log(stderr);
                 }
             });
}

//If template is a format string, processes it with x as the
//replacement. Otherwise, just evaluates.
//Limited to one replacement at most.
function make_output(template, x) {
    if(/.*\%s.*/i.test(template)) {
        var util = require('util');
        return util.format(template,x);
    }
    else {
        return template;
    }
}

Basically, the command it ends up building is something like:

touch Joe.java && echo -e `bunch of template stuffs` &> Joe.java && emacsclient Joe.java

Now, the problem I am getting is that the above command relies on output redirection, which my invoke command doesn't deal with very well - specifically, everything executes, but I get an empty file! Is there a way I can change either invoke or what I'm constructing to be invoked to avoid this problem?

Upvotes: 1

Views: 1825

Answers (1)

Louis
Louis

Reputation: 151511

The issue is that Node's child_process.exec starts sh but you are using features that are peculiar to bash. The &> is interpreted as & > in sh (two operators: a control operator and a redirection operator) and echo -e will use sh's builtin implementation of echo, which does not understand -e.

It would probably be possible to work around the issues above but using the shell like you do is fragile. For instance if your template contains single quotes ('), these quotes may interfere with the single quotes you use in your command. A more robust way to do it would be to change the main part of your code to use fs.writeFileSync rather than using shell commands to write to your file:

var templates = require('./config/template.json'),
    fs = require("fs"),
    args = process.argv;

if (args.length < 4) {
    console.log("Not enough arguments!");
}
else {
    var type = args[2],
        name = args[3];
    if (type in templates) {
        var tmpl = templates[type],
            contents = make_output(tmpl["format"],name),
            file_name = name + tmpl["extension"],
            command = "emacsclient " + file_name;

        fs.writeFileSync(file_name, contents);

        invoke(command);
    }
    else {
        console.log("No template for %s", type);
    }
}

You'd also want to modify make_output to perform the transformations that echo -e would have done for you.

Upvotes: 3

Related Questions