In Node, the child_process module is very important. Mastering it opens a new door in the World of Node.

Here’s a simple example:

const spawn = require('child_process').spawn;
const ls = spawn('ls'['-lh'.'/usr']);

ls.stdout.on('data', (data) => {
  console.log(`stdout: ${data}`);
});

ls.stderr.on('data', (data) => {
  console.log(`stderr: ${data}`);
});

ls.on('close', (code) => {
  console.log(`child process exited with code ${code}`);
});Copy the code

Several ways to create child processes

Matters needing attention:

  • The following are all ways to create a child process asynchronously, each of which has a synchronous version.
  • .exec(),.execFile(),.fork()The bottom is all through.spawn()The implementation.
  • .exec(),execFile()Additional callbacks are provided to execute when the child process stops.

child_process.spawn(command[, args][, options]) child_process.exec(command[, options][, callback]) child_process.execFile(file[, args][, options][, callback]) child_process.fork(modulePath[, args][, options])

child_process.exec(command[, options][, callback])

Create a shell and execute commands inside the shell. After execution, pass stdout and stderr as arguments to the callback method.

spawns a shell and runs a command within that shell, passing the stdout and stderr to a callback function when complete.

Examples are as follows:

  1. Successful execution,errorfornull; Execution failed,errorforErrorInstance.error.codeIs the error code,
  2. stdout,stderrIs standard output, standard error. The default is a string, unlessoptions.encodingforbuffer
var exec = require('child_process').exec; // Success storiesexec('ls -al'.function(error, stdout, stderr){
    if(error) {
        console.error('error: ' + error);
        return;
    }
    console.log('stdout: ' + stdout);
    console.log('stderr: '+ typeof stderr); }); // Failed examplesexec('ls hello.txt'.function(error, stdout, stderr){
    if(error) {
        console.error('error: ' + error);
        return;
    }
    console.log('stdout: ' + stdout);
    console.log('stderr: ' + stderr);
});Copy the code

Parameter Description:

  • cwd: Indicates the current working path.
  • env: Environment variables.
  • encoding: Encoding, defaultutf8.
  • shell: the shell used to execute commands, the default on Unix/bin/shOn Windows, the default iscmd.exe.
  • timeout: The default value is 0.
  • killSignalDefault is:SIGTERM.
  • uid: UID of the executing process.
  • gid: GID of the execution process.
  • maxBuffer: Maximum amount of data (in bytes) allowed for standard output, error output, if exceeded, the child process will be killed. Default: 200*1024

Remark:

  1. iftimeoutGreater than zero, then, when the child process runs greater thantimeoutMilliseconds, then, are sent to the processkillSignalSpecified signals (e.gSIGTERM).
  2. If it runs without error, thenerrorfornull. If something goes wrong, then,error.codeExist Code,error.signalIs set to terminate the process. (e.g.,CTRL+CWhen sendingSIGINT)

Risk items

Incoming commands, if entered by the user, may pose risks similar to SQL injection, such as

exec('ls hello.txt; rm -rf *'.function(error, stdout, stderr){
    if(error) {
        console.error('error: ' + error);
        // return;
    }
    console.log('stdout: ' + stdout);
    console.log('stderr: ' + stderr);
});
Copy the code

Note the matters

Note: Unlike the exec(3) POSIX system call, child_process.exec() does not replace the existing process and uses a shell to execute the command.

child_process.execFile(file[, args][, options][, callback])

Similar to.exec(), except that a new shell is not created. There are at least two implications

  1. thanchild_process.exec()It’s more efficient. (Actual to be tested)
  2. Some operations, such as I/O redirection, file glob, etc. are not supported.

similar to child_process.exec() except that it spawns the command directly without first spawning a shell.

File: specifies the name or path of the executable file.

Example:

var child_process = require('child_process');

child_process.execFile('node'['--version'].function(error, stdout, stderr){
    if(error){
        throw error;
    }
    console.log(stdout);
});

child_process.execFile('/ Users/a /. NVM/versions/node/v6.1.0 / bin/node'['--version'].function(error, stdout, stderr){
    if(error){
        throw error;
    }
    console.log(stdout);
});Copy the code

====== Expand reading =======

From the node source code, the biggest difference between exec() and execFile() is whether or not a shell is created. (inside execFile(), options.shell === false), then the shell can be set manually. The following code is almost equivalent. The shell Settings in WIN are different. If you are interested, you can try them out for yourself.

Note: execFile() is ultimately implemented internally via spawn(), if {shell: ‘/bin/bash’} is not set, the command is parsed differently inside spawm(), execFile(‘ ls-al.’) will report an error.

var child_process = require('child_process');
var execFile = child_process.execFile;
var exec = child_process.exec;

exec('ls -al .'.function(error, stdout, stderr){
    if(error){
        throw error;
    }
    console.log(stdout);
});

execFile('ls -al .', {shell: '/bin/bash'}, function(error, stdout, stderr){
    if(error){
        throw error;
    }
    console.log(stdout);
});Copy the code

child_process.fork(modulePath[, args][, options])

ModulePath: module in which the child process runs.

Parameter description :(duplicate parameter description is not listed here)

  • execPath: The executable used to create the child process/usr/local/bin/node. In other words, you can passexecPathTo specify the specific node executable path. (such as multiple Node versions)
  • execArgv: a list of string arguments passed to the executable. The default isprocess.execArgvTo be consistent with the parent process.
  • silentDefault is:falseThat is, of the child processstdioInherits from the parent process. If it istrueThe directpipeOf the child processchild.stdin,child.stdoutAnd so on.
  • stdio: If statedstdio, will overwritesilentOption Settings.

Example 1: Silent

parent.js

var child_process = require('child_process'); // Output from the child is printedfalse// Inherit from parent child_process.fork('./child.js', {
    silent: false}); // Example 2: Output from the silent child is not printedtrue// pipe to child_process.fork('./silentChild.js', {
    silent: true}); Output from another silent child var child = child_process.fork('./anotherSilentChild.js', {
    silent: true
});

child.stdout.setEncoding('utf8');
child.stdout.on('data'.function(data){
    console.log(data);
});Copy the code

child.js

console.log('output from the child');Copy the code

silentChild.js

console.log('output from the silent child');Copy the code

anotherSilentChild.js

console.log('output from another silent child');Copy the code

Example 2: IPC

parent.js

var child_process = require('child_process');

var child = child_process.fork('./child.js');

child.on('message'.function(m){
    console.log('message from child: ' + JSON.stringify(m));
});

child.send({from: 'parent'});Copy the code

child.js

process.on('message'.function(m){
    console.log('message from parent: ' + JSON.stringify(m));
});

process.send({from: 'child'});Copy the code

The results

➜ IPC git:(Master) Qualify node parent.js message from child: {"from":"child"}
message from parent: {"from":"parent"}Copy the code

Example 3: execArgv

First, see here for the definition of process.execArgv. The general purpose of setting up execArgv is to keep the child in the same execution environment as the parent.

For example, if the parent process specifies harmony, if the child process does not specify harmony, it will kneel.

parent.js

var child_process = require('child_process');

console.log('parent execArgv: ' + process.execArgv);

child_process.fork('./child.js', {
    execArgv: process.execArgv
});Copy the code

child.js

console.log('child execArgv: ' + process.execArgv);Copy the code

The results

execArgv git:(master) qualify node --harmony parent-js parentexecArgv: --harmony
child execArgv: --harmonyCopy the code

Example 3: execPath (TODO to be cited)

child_process.spawn(command[, args][, options])

Command: Indicates the command to be executed

Options Parameter description:

  • argv0: [String] This item is weird, it works differently on UNinx and Windows. We need to dig a little deeper.
  • stdio: (Array) | [String] the child stdio. referencehere
  • detached: [Boolean] Lets the child process run independently of its parent. The existing differences are also listed on different platforms for specific referencehere
  • shell| : [Boolean] [String] if it istrueRun the program in the shell. The default isfalse. (Useful, for example, to implement.exec() with /bin/sh -c XXX)

Example 1: Basic example

var spawn = require('child_process').spawn;
var ls = spawn('ls'['-al']);

ls.stdout.on('data'.function(data){
    console.log('data from child: ' + data);
});


ls.stderr.on('data'.function(data){
    console.log('error from child: ' + data);
});

ls.on('close'.function(code){
    console.log('child exists with code: ' + code);
});Copy the code

Example 2: Declare STDIO

var spawn = require('child_process').spawn;
var ls = spawn('ls'['-al'], {
    stdio: 'inherit'
});

ls.on('close'.function(code){
    console.log('child exists with code: ' + code);
});Copy the code

Example 3: Declare using a shell

var spawn = require('child_process').spawn; / / runecho "hello nodejs" | wc
var ls = spawn('bash'['-c'.'echo "hello nodejs" | wc'], {
    stdio: 'inherit',
    shell: true
});

ls.on('close'.function(code){
    console.log('child exists with code: ' + code);
});Copy the code

Example 4: Error handling, which contains two scenarios that have different handling styles.

  • Scenario 1: The command does not exist, and an error occurs when creating a child process.
  • Scenario 2: The command exists, but an error occurs during execution.
var spawn = require('child_process').spawn;
var child = spawn('bad_command');

child.on('error', (err) => {
  console.log('Failed to start child process 1.');
});

var child2 = spawn('ls'['nonexistFile']);

child2.stderr.on('data'.function(data){
    console.log('Error msg from process 2: ' + data);
});

child2.on('error', (err) => {
  console.log('Failed to start child process 2.');
});Copy the code

The result is as follows.

➜ spawn git:(master) Qualify node error/error.js Failed to start child process 1. Error MSG from Process 2: ls: nonexistFile: No such file or directoryCopy the code

Example 5: echo “hello nodejs” | grep “nodejs”

// echo "hello nodejs" | grep "nodejs"
var child_process = require('child_process');

var echo = child_process.spawn('echo'['hello nodejs']);
var grep = child_process.spawn('grep'['nodejs']);

grep.stdout.setEncoding('utf8');

echo.stdout.on('data'.function(data){
    grep.stdin.write(data);
});

echo.on('close'.function(code){
    if(code! ==0){ console.log('echo exists with code: ' + code);
    }
    grep.stdin.end();
});

grep.stdout.on('data'.function(data){
    console.log('grep: ' + data);
});

grep.on('close'.function(code){
    if(code! ==0){ console.log('grep exists with code: '+ code); }});Copy the code

Running results:

➜ spawn Git :(Master) Qualify node pipe/pipe.js grep: hello nodejsCopy the code

About the options. The stdio

Default: [‘pipe’, ‘pipe’, ‘pipe’], which means:

  1. Child. stdin, child.stdout are notundefined
  2. You can listen indataEvent to retrieve data.

Based on example

var spawn = require('child_process').spawn;
var ls = spawn('ls'['-al']);

ls.stdout.on('data'.function(data){
    console.log('data from child: ' + data);
});

ls.on('close'.function(code){
    console.log('child exists with code: ' + code);
});Copy the code

Write to child.stdin.write()

var spawn = require('child_process').spawn;
var grep = spawn('grep'['nodejs']);

setTimeout(function(){
    grep.stdin.write('hello nodejs \n hello javascript');
    grep.stdin.end();
}, 2000);

grep.stdout.on('data'.function(data){
    console.log('data from grep: ' + data);
});

grep.on('close'.function(code){
    console.log('grep exists with code: ' + code);
});Copy the code

Asynchronous vs Synchronous

Most of the time, child processes are created asynchronously. That is, it does not block the current event loop, which is helpful for performance.

Of course, there are times when a synchronous approach is more convenient (blocking the event loop), such as when executing shell scripts through child processes.

Node also provides synchronized versions, such as:

  • spawnSync()
  • execSync()
  • execFileSync()

About the options. Detached

Since I didn’t test it on Windows, I posted the original text first

On Windows, setting options.detached to true makes it possible for the child process to continue running after the parent exits. The child will have its own console window. Once enabled for a child process, it cannot be disabled.

A non-window is a platform representation

On non-Windows platforms, if options.detached is set to true, the child process will be made the leader of a new process group and session. Note that child processes may continue running after the parent exits regardless of whether they are detached or not. See setsid(2) for more information.

Default: The parent waits for the child to finish.

The child process. As you can see, there’s a timer running

var times = 0;
setInterval(function(){
    console.log(++times);
}, 1000);Copy the code

Run the following code to see that the parent process is holding on.

var child_process = require('child_process');
child_process.spawn('node'['child.js'], {
    // stdio: 'inherit'
});
Copy the code

Exit the parent process with child.unref()

Call child.unref() to remove the child from the parent’s event loop. The parent process can then exit happily. There are a few key points here

  1. callchild.unref()
  2. Set up thedetachedfortrue
  3. Set up thestdioforignore(Easy to forget)
var child_process = require('child_process');
var child = child_process.spawn('node'['child.js'], {
    detached: true,
    stdio: 'ignore'// Note: If it is not ignore, the parent process will not exit // stdio:'inherit'
});

child.unref();Copy the code

Redirect stdio to a file

Instead of setting stdio to ignore directly, you can redirect it to a local file.

var child_process = require('child_process');
var fs = require('fs');

var out = fs.openSync('./out.log'.'a');
var err = fs.openSync('./err.log'.'a');

var child = child_process.spawn('node'['child.js'], {
    detached: true,
    stdio: ['ignore', out, err]
});

child.unref();Copy the code

Difference between exec() and execFile()

First, exec() internally calls execFile() for implementation, and execFile() internally calls spawn() for implementation.

exec() -> execFile() -> spawn()

Second, execFile() internally defaults to false for options.shell and exec() does not.

Class: ChildProcess

  • throughchild_process.spawn()And so on, usually not directly with the constructor to create.
  • inheritedEventEmitters, so there are.on()Methods.

Various events

close

Triggered when the STdio stream is off. This event is different from exit because multiple processes can share the same STDIO stream. Parameters: code (exit code if the child process exits by itself), signal (signal to terminate the child process) Question: Must code exist? For example, if you kill a child process, what is code?

exit

Parameters: code, signal, code is the exit code if the child process exits by itself, otherwise null; Signal is the signal to terminate the process if the child process is terminated by a signal, otherwise null. One of the two is definitely not null. Note: The child’s STdio Stream may still be open when the exit event is triggered. (Scene?) In addition, NodeJS listens for SIGINT and SIGTERM signals, meaning that nodeJS does not immediately exit when it receives them, but does some cleanup and then rethrows them. At this point, js can clean up, such as closing the database, etc. TODO question: js can also not exit ????)

SIGINT: interrupt, a program termination signal, usually emitted when the user presses CTRL+C, to inform the foreground process to terminate the process. SIGTERM: terminate, a program termination signal that can be blocked and processed, usually to require the program itself to exit normally. The shell command kill generates this signal by default. If the signal cannot be terminated, we try SIGKILL (forced termination).

Also, note that Node.js establishes signal handlers for SIGINT and SIGTERM and Node.js processes will not terminate immediately due to receipt of those signals. Rather, Node.js will perform a sequence of cleanup actions and then will re-raise the handled signal.

error

An error is raised when the following occurs. Exit may or may not fire when error does. (Inside is broken)

  • Unable to create child process.
  • The process cannot be killed. (TODO example)
  • Failed to send message to child process. (TODO example)

message

Triggered when process.send() is used to send a message. Parameters: Message, which is a JSON object, or Primitive Value; SendHandle, net.socket object, or net.server object (when is TODO??)

.connected: Set to false when.disconnected() is called. Represents whether messages can be received from or sent to child processes.

.disconnect() : closes the IPC channel between parent and child processes. When this method is called, the Disconnect event fires. If the child process is a Node instance (created by child_process.fork()), then an active call to process.disconnect() can also be made inside the child process to terminate the IPC channel. Refer to the process. Disconnect. Question: If a child is forked and the child starts the HTTP server, what is the effect of the parent’s.disconnect() call? (TODO verification??)

Non-important cheat points

CMD and BAT on Windows

The importance of the distinction between child_process.exec() and child_process.execFile() can vary based on platform. On Unix-type operating systems (Unix, Linux, OSX) child_process.execFile() can be more efficient because it does not spawn a shell. On Windows, however, .bat and .cmd files are not executable on their own without a terminal, and therefore cannot be launched using child_process.execFile(). When running on Windows, .bat and .cmd files can be invoked using child_process.spawn() with the shell option set, with child_process.exec(), or by spawning cmd.exe and passing the .bat or .cmd file as an argument (which is what the shell option and child_process.exec() do).

// On Windows Only ...
const spawn = require('child_process').spawn;
const bat = spawn('cmd.exe'['/c'.'my.bat']);

bat.stdout.on('data', (data) => {
  console.log(data);
});

bat.stderr.on('data', (data) => {
  console.log(data);
});

bat.on('exit', (code) => {
  console.log(`Child exited with code ${code}`);
});

// OR...
const exec = require('child_process').exec;
exec('my.bat', (err, stdout, stderr) => {
  if (err) {
    console.error(err);
    return;
  }
  console.log(stdout);
});Copy the code

Process the title

Note: Certain platforms (OS X, Linux) will use the value of argv[0] for the process title while others (Windows, SunOS) will use command.

Note: Node.js currently overwrites argv[0] with process.execPath on startup, so process.argv[0] in a Node.js child process will not match the argv0 parameter passed to spawn from the parent, retrieve it with the process.argv0 property instead.

A problem with the running order of the code

p.js

const cp = require('child_process');
const n = cp.fork(`${__dirname}/sub.js`);

console.log('1');

n.on('message', (m) => {
  console.log('PARENT got message:', m);
});

console.log('2');

n.send({ hello: 'world' });

console.log('3');Copy the code

sub.js

console.log('4');
process.on('message', (m) => {
  console.log('CHILD got message:', m);
});

process.send({ foo: 'bar' });
console.log('5');Copy the code

Run node p.js and print the following

➜ ch node p.js 1 2 3 4 5 PARENT got message: {foo:'bar' }
CHILD got message: { hello: 'world' }Copy the code

Let’s do another example

// p2.js
var fork = require('child_process').fork;

console.log('p: 1');

fork('./c2.js');

console.log('p: 2'); // From the test results, sometimes the timer callback is executed before the child process, and sometimes it is executed slower than the child process. const t = 70;setTimeout(function(){
    console.log('p: 3 in %s', t);
}, t);


// c2.js
console.log('c: 1');Copy the code

About NODE_CHANNEL_FD

When child_process.fork(), if execPath is specified, parent and child processes communicate via NODE_CHANNEL_FD.

Node.js processes launched with a custom execPath will communicate with the parent process using the file descriptor (fd) identified using the environment variable NODE_CHANNEL_FD on the child process. The input and output on this fd is expected to be line delimited JSON objects.

A link to the

Official documentation: nodejs.org/api/child_p…