12. Command-Line Programming

After spending so much time in this book discussing how Node.js is a powerful asynchronous, nonblocking platform for creating applications, I’m now going to show you how sometimes using regular synchronous, blocking IO is also cool in Node—writing command-line programs. It turns out that writing JavaScript code is so much fun that people are starting to write a lot of their day-to-day scripts and other quick programs in Node as well.

In this chapter, you look at the basics of running command-line scripts in Node, including looking at some of the synchronous file system APIs available to you. You then look at how you can interact with the user directly via buffered and unbuffered input, and finally look at process creation and execution.

Running Command-Line Scripts

You have quite a few options to run Node.js scripts from the command line, for both UNIX-like and Mac operating systems as well as Windows. You are able to make them appear as regular operating system commands and pass command-line parameters to them as well.

UNIX and Mac

On UNIX-like operating systems such as Linux or Mac OS X, a majority of command-line users use the Bourne Shell, sh (or the popular modern variant, bash). Although the shell scripting language it comes with is quite full featured and powerful, it can sometimes still be more fun to write scripts in JavaScript instead.

You have two ways to run your Node programs in the shell. The easiest is to just run a program as you always have—by specifying the node program and the script to run (the .js extension is optional, actually):

node do_something

The second is to directly make the script an executable file by changing the first line as follows:

#!/usr/local/bin/node

The first two characters are known as a shebang and tell the operating system that what comes directly after it is the path of the program to execute with this file as a given argument. In the preceding example, the operating system knows to go to /usr/local/bin, look for the executable node, run it, and pass in the file as an argument to Node. The Node interpreter is smart enough to ignore this first line starting with #! because it realizes that this line is for operating system use.

So, you can write the following hello world executable in Node:

#!/usr/local/bin/node
console.log("Hello World!");

To run the program as an executable, you also need to mark it as an executable file by using the chmod command:

chmod 755 hello_world

Now you can directly execute the script by typing (the ./ tells the shell to look in the current folder for the script)

$ ./hello_world
Hello World!
$

There is one small problem with this approach: what happens when you take your script to other machines and node is not in /usr/local/bin, but instead in /usr/bin? The common solution is to change the shebang line as follows:

#!/usr/bin/env node

The env command then looks through your PATH environment variable to find the first instance of the node program and then runs that. All UNIX-like and Mac machines have env in /usr/bin, so as long as node is in the system PATH somewhere, this is more portable.

If you poke around in your various operating system executable directories or other places where you have installed software, you see that a lot of programs you use on a regular basis are, in fact, scripts of some sort.

One small bonus: the Node interpreter is smart enough, even on Windows, to ignore the #! syntax, so you can still write scripts that can be run as executables on both platforms!

Windows

Microsoft Windows has never really had a fantastic scripting environment available to it. Windows Vista and 7 introduced the PowerShell, which is a nice improvement on previous ones, but it still never quite feels as powerful and easy to use as the shells available on UNIX-like platforms. The good news, however, is that this doesn’t hurt you that much because your primary goal is just to get your scripts launched and executing, and the features available in all modern (and semimodern) versions of Windows are more than enough.

You can run your scripts with batch files (carrying the extension .bat). If you have the simple hello_world.js you saw in the previous section, you could create hello_world.bat, as follows:

@echo off
node hello_world

By default, Windows echoes every command it sees in a batch file. The @echo off tells it to stop that. Now, to run the program, you could just type

C:UsersMarkLearningNodeChapter11> hello_world
Hello World!
C:UsersMarkLearningNodeChapter11>

But there is a bug in this program! What if hello_world.bat and hello_world.js are in the same folder, but you are executing the batch file from somewhere else, as shown here:

C:UsersMarkLearningNodeChapter11> cd
C:> UsersMarkLearningNodeChapter11hello_world
module.js:340
    throw err;
          ^
Error: Cannot find module 'C:hello_world'
    at Function.Module._resolveFilename (module.js:338:15)
    at Function.Module._load (module.js:280:25)
    at Module.runMain (module.js:492:10)
    at process.startup.processNextTick.process._tickCallback (node.js:244:9)

Because you don’t specify the full path to hello_world.js, Node doesn’t know where to look for it and gets lost. You fix that problem by changing the hello_world.bat script to be as follows:

@echo off
node %~d0\%~p0hello_world

Here, you make use of two handy little batch file macros, %-pXX and %~dXX, to give you the drive letter and path, respectively, of the nth argument (here, the 0th, or the name of the script being run). Now you can run hello_world from wherever you want on your computer and it’ll work properly:

Z:> C:UsersMarkLearningNodeChapter11hello_world
Hello World!

Scripts and Parameters

To be able to do interesting things with your scripts, you need to pass them parameters. In this case you need to worry about two problems: how to pass these parameters to your scripts and how to access them within the script.

If you run your Node scripts by calling the interpreter all the time (which works on all platforms), you need to do nothing extra—the arguments are passed directly to the executing script:

node script_name [args]*

Passing Parameters on UNIX/Mac

On UNIX-like operating systems where you use the #! syntax to launch your scripts, these parameters are passed directly to your scripts when you run them. So even here you don’t have to do anything else.

Passing Parameters on Windows

On Windows, you mostly run your Node scripts via batch files with the .bat extension. You can pass parameters to these batch files, and they can, in turn, pass them on to your Node.js scripts by using the macro %*. So, the batch file would look as follows:

@echo off
node %~d0\%~p0params %*

Accessing Parameters in Node

All parameters passed to your scripts are appended to the argv array on the process global object. The first two items in this array are always the node interpreter running and the script being run. So, any additional arguments passed to the script start at the third element, index 2. Now run the following:

#!/usr/local/bin/node
console.log(process.argv);

You should see the output roughly as follows:

Kimidori:01_running marcw$ ./params.js 1 2 3 4 5
[ '/usr/local/bin/node',
  '/Users/marcw/src/misc/LearningNode/Chapter11/01_running/params.js',
  '1',
  '2',
  '3',
  '4',
  '5' ]

Working with Files Synchronously

For nearly every single API you have seen in the file system module (fs), both asynchronous and synchronous versions are available. You have mostly been using the asynchronous versions thus far, but the synchronous versions can be extremely useful to you when writing command-line programs. Basically, for a vast majority of APIs in fs with the name func, there is a corresponding API called funcSync.

In the following sections, you look at a few examples of how you can use some of these synchronous APIs.

Basic File APIs

The fs module doesn’t have a file copy function, so you can write one yourself. For the open, read, and write APIs, you use the synchronous versions: openSync, readSync, and writeSync. In the synchronous versions, they throw an error if anything goes wrong. You can use a buffer to hold the data as you copy from location a to b. Be sure to close everything when you’re done:

var BUFFER_SIZE = 1000000;

function copy_file_sync (src, dest) {
    var read_so_far, fdsrc, fddest, read;
    var buff = new Buffer(BUFFER_SIZE);

    fdsrc = fs.openSync(src, 'r');
    fddest = fs.openSync(dest, 'w');
    read_so_far = 0;

    do {
        read = fs.readSync(fdsrc, buff, 0, BUFFER_SIZE, read_so_far);
        fs.writeSync(fddest, buff, 0, read);
        read_so_far += read;
    } while (read > 0);

    fs.closeSync(fdsrc);
    return fs.closeSync(fddest);
}

To call this function, you can write a file_copy.js script that makes sure it gets enough arguments, calls the copy function, and then handles any errors that are thrown during the process:

if (process.argv.length != 4) {
    console.log("Usage: " + path.basename(process.argv[1], '.js')
                + " [src_file] [dest_file]");
} else {
    try {
        copy_file_sync(process.argv[2], process.argv[3]);
    } catch (e) {
        console.log("Error copying file:");
        console.log(e);
        process.exit(-1);
    }

    console.log("1 file copied.");
}

You can see here that you also make use of a new function, process.exit. This function terminates the Node.js program immediately and passes the return code back to the calling program (often a shell interpreter or command prompt). The standard in Bourne shell (sh) or its improved version Bourne Again Shell (bash) is to return 0 on success and nonzero when something bad happens. You return –1 when something goes wrong with the copy.

You can trivially change this copy function to a move function by first performing the file copy operation and then deleting the source file when you’re sure the destination has been fully written and successfully closed. You perform this deletion by using the function unlinkSync:

function move_file_sync (src, dest) {
    var read_so_far, fdsrc, fddest, read;
    var buff = new Buffer(BUFFER_SIZE);

    fdsrc = fs.openSync(src, 'r');
    fddest = fs.openSync(dest, 'w');
    read_so_far = 0;

    do {
        read = fs.readSync(fdsrc, buff, 0, BUFFER_SIZE, read_so_far);
        fs.writeSync(fddest, buff, 0, read);
        read_so_far += read;
    } while (read > 0);

    fs.closeSync(fdsrc);
    fs.closeSync(fddest);
    return fs.unlinkSync(src);
}

The rest of the script remains the same, except you change the call to copy_file_sync to move_file_sync.

Files and Stats

You create folders in Node by using the mkdir function in the file system module. In the scripts shown here, you use the mkdirSync variation of it. Now you’re ready to write a program that behaves the same as the mkdir -p command in UNIX shells: you specify a full path, and it creates that directory and also any missing intermediary directories that do not yet exist.

The work takes place in two steps:

1. You break apart the path and then, starting at the top, see which directories already exist or not. If something exists at the given location but isn’t a directory (that is, you want to use mkdir a/b/c, but a/b already exists and is a regular file), you throw an error. To determine the existence of a file, you use the existsSync function and then call the statsSync function, which gives you a Stats object that can tell you whether or not the object is a directory.

2. For all those entries that have not yet been created, you iterate through and create them.

Here’s the code for the mkdirs function:

function mkdirs (path_to_create, mode) {
    if (mode == undefined) mode = 0777 & (~process.umask());

    // 1. What do we have already or not?
    var parts = path_to_create.split(path.sep);
    var i;
    for (i = 0; i < parts.length; i++) {
        var search;
        search = parts.slice(0, i + 1).join(path.sep);
        if (fs.existsSync(search)) {
            var st;
            if ((st = fs.statSync(search))) {
                if (!st.isDirectory())
                    throw new Error("Intermediate exists, is not a dir!");
            }
        } else {
            // doesn't exist. We can start creating now
            break;
        }
    }

    // 2. Create whatever we don't have yet.
    for (var j = i; j < parts.length; j++) {
        var build = parts.slice(0, j + 1).join(path.sep);
        fs.mkdirSync(build, mode);
    }
}

The first line of this function, to set up the mask (the default permissions for file system objects), is just a way for you to choose reasonable permissions for the new directories. The caller of this function can specify them directly or can use what is known as the umask to filter out those permissions that the shell environment variable of the current user does not want to give to new files (or directories). On Windows machines, umask returns 0, indicating that it does not want to mask anything out at all; Windows uses a completely different mechanism for file permissions.

Listing Contents of Directories

To list the contents of a directory, you can use the readdirSync function, which returns an array of all filenames in the specified folder, excluding "." and "..":

#!/usr/local/bin/node
var fs = require('fs');

var files = fs.readdirSync(".");
console.log(files);

Interacting with the User: stdin/stdout

You might be familiar with the trio of IO handles given to all processes, stdin, stdout, and stderr, representing input, normal output, and error output, respectively. These are all available to the Node.js scripts and hang off the process object. In Node, they are all instances of Stream objects (see Chapter 6, “Expanding Your Web Server”).

The console.log function, in fact, is just the equivalent of

process.stdout.write(text + " ");

Whereas console.error is basically

process.stderr.write(text + " ");

Input, however, gives you a few more options, so in the following sections, you look at buffered (line-by-line) input versus unbuffered (type a character and immediately see something) input.

Basic Buffered Input and Output

By default, the stream you get for stdin reads and buffers one line at a time. Thus, if you try to read from stdin, nothing happens until the user presses the Enter key, and then your program is given the entire line read from the input stream. You listen for these events by adding a handler for the readable event on stdin, as follows:

process.stdin.on('readable', function () {
    var data = process.stdin.read();
    // do something w input
});

Let’s write a little program that reads in a line of input and then generates and prints out an md5 hash of this input, looping until the user either presses Ctrl+C or enters a blank line:

process.stdout.write("Hash-o-tron 3000 ");
process.stdout.write("(Ctrl+C or Empty line quits) ");
process.stdout.write("data to hash > ");

process.stdin.on('readable', function () {
    var data = process.stdin.read();
    if (data == null) return;
    if (data == " ") process.exit(0);

    var hash = require('crypto').createHash('md5');
    hash.update(data);
    process.stdout.write("Hashed to: " + hash.digest('hex') + " ");
    process.stdout.write("data to hash > ");
});

process.stdin.setEncoding('utf8');

Most of the work happens when the user presses Enter: you check to see whether the input is nonempty. If it is, you hash it, print out the result, and then print another prompt and wait for the user to type in something else. Because stdin is not in the paused state, the Node program does not exit. (If you were to pause it, it would exit if there was nothing else to do.)

Unbuffered Input

For those situations in which you want to let the user simply press a key and have something happen immediately, you can turn on raw mode on the stdin stream, using the setRawMode function, which takes a Boolean indicating whether or not it should turn on (true) raw mode.

Update your hash generator for the previous section to let the user select which type of hash she would like. After the user enters a line of text and presses Enter, you ask her to press a number between 1 and 4 to choose which algorithm she’d like. The complete listing for this program is shown in Listing 11.1.

Listing 11.1 Using Raw Mode on Stdin (raw_mode.js)


process.stdout.write("Hash-o-tron 3000 ");
process.stdout.write("(Ctrl+C or Empty line quits) ");
process.stdout.write("data to hash > ");

process.stdin.on('readable', function (data) {
    var data = process.stdin.read();
    if (!process.stdin.isRaw) {                          // 1.
        if (data == " ") process.exit(0);
        process.stdout.write("Please select type of hash: ");
        process.stdout.write("(1 – md5, 2 – sha1, 3 – sha256, 4 – sha512) ");
        process.stdout.write("[1-4] > ");
        process.stdin.setRawMode(true);
    } else {
        var alg;
        if (data != '^C') {                               // 2.
            var c = parseInt(data);
            switch (c) {
                case 1: alg = 'md5'; break;
                case 2: alg = 'sha1'; break;
                case 3: alg = 'sha256'; break;
                case 4: alg = 'sha512'; break;
            }
            if (alg) {                                   // 3.
                var hash = require('crypto').createHash(alg);
                hash.update(data);
                process.stdout.write(" Hashed to: " + hash.digest('hex'));
                process.stdout.write(" data to hash > ");
                process.stdin.setRawMode(false);
            } else {
                process.stdout.write(" Please select type of hash: ");
                process.stdout.write("[1-4] > ");
            }
        } else {
            process.stdout.write(" data to hash > ");
            process.stdin.setRawMode(false);
        }
    }
});

process.stdin.setEncoding(;'utf8')


Because this script receives input from stdin both when it is buffered (when you’re asking for the text to hash) and when it is unbuffered (when you ask for the hashing algorithm to use), it’s a bit more involved. Let’s see how it works:

1. You first check to see whether the input you received is buffered or unbuffered. If the former, it’s a line of text to hash, and you should print out the request to choose which algorithm to use. Because you want the user to just be able to press a number between 1 and 4, you switch stdin to raw mode now so that pressing any key triggers the readable event.

2. If the stream is in raw mode, it means the user has pressed a key in response to your request for a hash algorithm. For the first line here, you check to see whether this key is Ctrl+C. (Note that this can be tricky to enter into a text editor; in Emacs, you can press Ctrl+Q and then Ctrl+C, and it types the ^C for you. Every editor is slightly different.) If the user pressed Ctrl+C, you abort the input request and go back to the hash prompt.

If the user entered some other key, you first make sure it’s a valid key (1–4) or else you squawk and make the user try again.

3. Finally, depending on the algorithm selected, you generate the hash, print it out, and then go back to the normal enter data prompt. You must be sure to turn off raw mode here so that you go back to line-buffered input!

Because we are working with stdin in this program, it does not exit until you call process.exit, the user enters a blank line, or the user presses Ctrl+C during buffered input (which causes Node to just terminate).

The readline Module

One other way to work with input in Node.js is to use the readline module. It has a couple of neat features that you can use in your programs.

To turn on the readline module, call the createInterface method on it, specifying as options the stdin and stdout streams to use:

var readline = require('readline');

var rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout
});

After you do this, your program does not exit until rl.close is called!

Line-by-Line Prompting

If you call the prompt method in readline, the program waits for a line of input (followed by Enter). When the program gets this keypress, it sends the line event on the readline object. The event can process it as needed:

rl.on("line", function (line) {
    console.log(line);
    rl.prompt();
});

To continue listening, you call the prompt method again. One of the nice things about the readline interface is that if the user presses Ctrl+C, you get the SIGINT event called, and you can either shut things down there or do other things to reset the state and continue. Here, by closing the readline interface, you cause the program to stop listening for input and exit:

rl.on("SIGINT", function () {
    rl.close();
});

Now you’re ready to write a little reverse Polish notation calculator using the readline module. The code for this calculator is shown in Listing 11.2.

If you’ve never heard of reverse Polish notation or forgotten how it works, it’s basically a postfix mathematical notation format. Instead of writing 1 + 2, you write 1 2 +. To write 5 * (2 + 3), you write 5 2 3 + *, and so on. The little calculator takes a string at a time, splits it up based on spaces, and does simple calculations.

Listing 11.2 Simple postfix calculator using readline (readline.js)


var readline = require('readline');
var rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout
});

var p = "postfix expression > "
rl.setPrompt(p, p.length);
rl.prompt();                                            // 1.

rl.on("line", function (line) {                          // 2.
    if (line == " ")  {
        rl.close(); return;
    }

    var parts = line.split(new RegExp("[ ]+"));
    var r = postfix_process(parts);
    if (r !== false)
        process.stdout.write("Result: " + r + " ");
    else
        process.stdout.write("Invalid expression. ");
    rl.prompt();                                        // 3.
});

rl.on("SIGINT", function () {                            // 4.
    rl.close();
});

// push numbers onto a stack, pop when we see an operator.
function postfix_process(parts) {
    var stack = [];
    for (var i = 0; i < parts.length; i++) {
        switch (parts[i]) {
          case '+': case '-': case '*': case '/':
            if (stack.length < 2) return false;
            do_op(stack, parts[i]);
            break;
          default:
            var num = parseFloat(parts[i]);
            if (isNaN(num)) return false;
            stack.push(num);
            break;
        }
    }
    if (stack.length != 1) return false;
    return stack.pop();
}

function do_op(stack, operator) {
    var b = stack.pop();
    var a = stack.pop();
    switch (operator) {
      case '+': stack.push(a + b); break;
      case '-': stack.push(a - b); break;
      case '*': stack.push(a * b); break;
      case '/': stack.push(a / b); break;
      default:  throw new Error("Unexpected operator");
    }
}


This program works as follows:

1. You create the readline module object, set the default prompt text on it, and then tell readline to show that prompt and wait for a line of input.

2. When you receive a line of input, you check to see whether it’s empty (in which case you close down the program by closing the readline interface), or else you parse the input string and pass it to the little calculating function. You print out the results (good or bad).

3. You complete the loop by telling readline to print out the prompt and wait again.

4. If the user presses Ctrl+C, you close down readline, which causes the program to exit gracefully.

Now you can test it:

hostnameKimidori:03_stdinout marcw$ node readline_rpn.js
postfix expression > 1 2 +
Result: 3
postfix expression > 2 3 4 + *
Result: 14
postfix expression > cat
Invalid expression.
postfix expression > 1 2 4 cat dog  3 4 + - / *
Invalid expression.
postfix expression > 2 3 + 5 *
Result: 25
postfix expression >

Questions

The other major functionality in the readline module is the ability to ask questions and receive the answers directly in a callback. The basic format is

rl.question("hello? ", function (answer) {
    // do something
});

Next, you write a little program that performs a survey: it takes an array of questions (which you could put in a file if you wanted to make it configurable) and asks the user to answer them one at a time. It then writes the user’s answers to answers.txt using appendFileSync on the fs module.

Note that because the question function is asynchronous, you have to use async.forEachSeries to iterate over each of the questions in the survey! The code for the survey program is shown in Listing 11.3.

Listing 11.3 Survey program (questions.js)


var readline = require('readline'),
    async = require("async"),
    fs = require('fs');

var questions = [ "What's your favorite color? ",
                  "What's your shoe size? ",
                  "Cats or dogs? ",
                  "Doctor Who or Doctor House? " ];

var rl = readline.createInterface({                    // 1.
    input: process.stdin,
    output: process.stdout
});

var output = [];
async.forEachSeries(
    questions,
    function (item, cb) {                              // 2.
        rl.question(item, function (answer) {
            output.push(answer);
            cb(null);
        });
    },
    function (err) {                                   // 3.
        if (err) {
            console.log("Hunh, couldn't get answers");
            console.log(err);
            return;
        }
        fs.appendFileSync("answers.txt", JSON.stringify(output) + " ");
        console.log(" Thanks for your answers!");
        console.log("We'll sell them to some telemarketer immediately!");
        rl.close();
    }
);


The program performs the following tasks:

1. It initializes the readline module and sets up the stdin and stdout streams.

2. Then for each question in the array, you call the question function on readline (using async.forEachSeries because question is an asynchronous function) and add the result to the output array.

3. Finally, after all the questions have been asked and async calls the results function, you either print the error if there was one or append the user’s output to the answers.txt file and then close the readline object to exit the program.

Working with Processes

One other thing you can do from the command line (or even from within your web applications) in Node is to launch other programs. You have a couple of options for this using the child_process module, with varying degrees of complexity. In the following sections, you start with the easier option, exec.

Simple Process Creation

The exec function in the child_process module takes a command and executes it in the system shell (sh/bash on UNIX/Mac, or cmd.exe on Windows). Thus, you can specify something simple like "date" as the program to run, or something complicated like "echo 'Mancy' | sed s/M/N/g". All commands are run, and all output is buffered and passed back to the caller after the command finishes.

The basic format is

exec(command, function (error, stdout, stderr) {
    // error is non-null if an error occurred
    // stdout and stderr are buffers
});

After the command finishes executing, the callback is called. The first parameter is non-null only if an error occurred. Otherwise, stdout and stderr are set to Buffer objects with the full contents of anything written to the respective output stream.

Now write a program that uses the exec function to run the cat program (type on Windows). It takes the name of a file to print out. It uses the exec function to launch the cat/type program and then prints out all the output when you’re done:

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

if (process.argv.length != 3) {
    console.log("I need a file name");
    process.exit(-1);
}

var file_name = process.argv[2];
var cmd = process.platform == 'win32' ? 'type' : "cat";
child = exec(cmd + " " + file_name, function (error, stdout, stderr) {
    console.log('stdout: ' + stdout);
    console.log('stderr: ' + stderr);
    if (error) {
        console.log("Error exec'ing the file");
        console.log(error);
        process.exit(1);
    }
});

Advanced Process Creation with Spawn

A more advanced way of creating processes is to use the spawn function available in the child_process module. This function gives you complete control over the stdin and stdout of the child processes you create and permits some fantastic functionality, such as piping output from one child process to another.

Start by writing a program that takes the name of a JavaScript file and runs it with the node program:

var spawn = require("child_process").spawn;
var node;

if (process.argv.length != 3) {
    console.log("I need a script to run");
    process.exit(-1);
}

var node = spawn("node", [ process.argv[2] ]);
node.stdout.on('readable', print_stdout);
node.stderr.on('readable', print_stderr);
node.on('exit', exited);

function print_stdout() {
    var data = process.stdout.read();
    console.log("stdout: " + data.toString('utf8'));
}
function print_stderr(data) {
    var data = process.stderr.read();
    console.log("stderr: " + data.toString('utf8'));
}
function exited(code) {
    console.error("--> Node exited with code: " + code);
}

When you want to call spawn, the first argument is the name of the command to execute, and the second is an array of parameters to pass to it. You can see that any output written to stdout or stderr by the child process immediately triggers an event on the appropriate stream, and you can see what’s happening right away.

Now you’re ready to write something a bit more advanced. In the preceding chapter, you saw a way to effectively do the following in shell scripts or command prompts:

while 1
    node script_name
end

Write that same code, but in JavaScript, using the spawn function. Using this function is actually a bit more work than writing it as a shell script, but it’s instructive and sets you up to do more work and monitoring as you want later. The code for the new launcher is shown in Listing 11.4.

Listing 11.4 An all-node node runner (node_runner.js)


var spawn = require("child_process").spawn;
var node;

if (process.argv.length < 3) {
    console.log("I need a script to run");
    process.exit(-1);
}

function spawn_node() {
    var node = spawn("node", process.argv.slice(2));
    node.stdout.on('readable', print_stdout);
    node.stderr.on('readable', print_stderr);
    node.on('exit', exited);
}

function print_stdout() {
    var data = process.stdout.read();
    console.log("stdout: " + data.toString('utf8'));
}
function print_stderr(data) {
    var data = process.stderr.read();
    console.log("stderr: " + data.toString('utf8'));
}
function exited(code) {
    console.error("--> Node exited with code: " + code + ". Restarting");
    spawn_node();
}

spawn_node();


This program works by listening for the exit event on the spawned process. Whenever it sees that, it restarts the node interpreter with the script you want to run.

I leave it to you as an exercise to update this script to behave like the full node_ninja_runner you wrote in Chapter 10. You can get the output of the ps aux command by using the exec function; the resulting buffer can easily be parsed in JavaScript. Finally, you can kill a child process by calling the kill method on it if you detect that it’s getting too large.

Summary

Now you’ve seen that Node is not only great for writing networked applications, but is also extremely powerful and fun for command-line synchronous applications. After this tour of creating and running scripts and passing parameters to them, you should now be pretty comfortable manipulating input and output in your program and even be able switch between buffered and unbuffered input when necessary. Finally, you should be able to create and run programs in your Node.js scripts with exec and spawn.

To finish this treatment of programming JavaScript in Node.js, it’s time now to turn your attention to testing your scripts and applications.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset