Let’s enhance our file-watching example program even further by having it spawn a child process in response to a change. To do this, we’ll bring in Node.js’s child-process module and dive into some Node.js patterns and classes. You’ll also learn how to use streams to pipe data around.
To keep things simple, we’ll make our script invoke the ls command with the -l and -h options. This will give us some information about the target file whenever it changes. You can use the same technique to spawn other kinds of processes, as well.
Open your editor and enter this:
| | 'use strict'; |
| | const fs = require('fs'); |
| | const spawn = require('child_process').spawn; |
| | const filename = process.argv[2]; |
| | |
| | if (!filename) { |
| | throw Error('A file to watch must be specified!'); |
| | } |
| | |
| | fs.watch(filename, () => { |
| | const ls = spawn('ls', ['-l', '-h', filename]); |
| | ls.stdout.pipe(process.stdout); |
| | }); |
| | console.log(`Now watching ${filename} for changes...`); |
Save the file as watcher-spawn.js and run it with node as before:
| | $ node watcher-spawn.js target.txt |
| | Now watching target.txt for changes... |
If you go to a different console and touch the target file, your Node.js program will produce something like this:
| | -rw-rw-r-- 1 jimbo jimbo 6 Dec 8 05:19 target.txt |
The username, group, and other properties of the file will be different, but the format should be the same.
Notice that we added a new require() at the beginning of the program. Calling require(’child_process’) returns the child process module.[18] We’re interested only in the spawn() method, so we save that to a constant with the same name and ignore the rest of the module.
| | spawn = require('child_process').spawn, |
Remember, functions are first-class citizens in JavaScript, so we’re free to assign them directly to variables like we did here.
Next, take a look at the callback function we passed to fs.watch():
| | () => { |
| | const ls = spawn('ls', ['-l', '-h', filename]); |
| | ls.stdout.pipe(process.stdout); |
| | } |
Unlike the previous arrow-function expression, this one has a multiline body; hence the opening and closing curly braces ({}).
The first parameter to spawn() is the name of the program we wish to execute; in our case it’s ls. The second parameter is an array of command-line arguments. It contains the flags and the target filename.
The object returned by spawn() is a ChildProcess. Its stdin, stdout, and stderr properties are Streams that can be used to read or write data. We want to send the standard output from the child process directly to our own standard output stream. This is what the pipe() method does.
Sometimes you’ll want to capture data from a stream, rather than just piping it forward. Let’s see how to do that.