This article mainly introduces Node. create and manage external processes in js. This article describes how to execute External commands and sub-processes, for more information about how to use Node to efficiently process I/O operations, you should know that some types of programs are not suitable for this mode. For example, if you plan to use Node to process a CPU-intensive task, you may block the event loop and reduce the program response. The alternative is to allocate CPU-intensive tasks to a separate process to release the event loop. Node allows you to generate a process and use the new process as the child process of its parent process. In Node, a sub-process can communicate with the parent process in two directions. To some extent, the parent process can also monitor and manage sub-processes.
Another scenario where sub-processes need to be used is when you want to simply execute an external command and let Node obtain the return value of the command. For example, you can execute a UNIX Command, script, or other commands that cannot be directly executed in Node.
This chapter will show you how to execute External commands, create, communicate with sub-processes, and terminate sub-processes. The focus is to let you know how to complete a series of tasks outside the Node process.
Execute External commands
When you need to execute an external shell command or executable file, you can use the child_process module to import it like this:
The Code is as follows:
Var child_process = require ('child _ Process ')
Then, you can use the exec function in the module to execute External commands:
The Code is as follows:
Var exec = child_process.exec;
Exec (command, callback );
The first parameter of exec is the shell command string you are about to execute, and the second parameter is a callback function. This callback function will be called when exec executes an external command or an error occurs. The callback function has three parameters: error, stdout, and stderr. See the following example:
The Code is as follows:
Exec ('Ls', function (err, stdout, stderr ){
// Note: if you are using windows, you can change it to a windows Command, such as dir.
});
If an Error occurs, the first parameter will be an Error instance. If the first parameter does not contain an Error, the second parameter stdout will contain the standard output of the command. The last parameter contains command-related error output.
List 8-1 shows a complex example of executing external commands
LISTING 8-1: Execute External commands (source code: chapter8/01_external_command.js)
The Code is as follows:
// Import the exec function of the child_process Module
Var exec = require('child_process'cmd.exe c;
// Call the "cat *. js | wc-l" command
Exec ('cat *. js | wc-l', function (err, stdout, stderr) {// row 4
// Command exit or call failure
If (err ){
// An error occurred while starting the external process.
Console. log ('child _ process exited, error code: ', err. code );
Return;
}
}
In the fourth line, we set "cat *. js | wc-l "is passed to exec as the first parameter. You can also try any other command, as long as you have used the command in shell.
Then, a callback function is used as the second parameter, which will be called when an error occurs or the child process ends.
You can also pass the third optional parameter before the callback function, which contains some configuration options, such:
The Code is as follows:
Var exec = require('child_process'cmd.exe c;
Var options = {
Timeout: 1000,
KillSignal: 'signatkill'
};
Exec ('cat *. js | wc-l', options, function (err, stdout, stderr ){
//...
});
The following parameters are available:
1. cwd -- current directory. You can specify the current working directory.
2. encoding -- encoding format of the output content of the child process. The default value is "utf8", that is, UTF-8 encoding. If the output of the sub-process is not utf8, you can use this parameter to set the supported encoding formats:
The Code is as follows:
Ascii
Utf8
Ucs2
Base64
For more information about the encoding formats supported by Node, see Chapter 4th "using Buffer for processing, encoding, and decoding binary data ".
1. timeout-time-out for command execution in milliseconds. The default value is 0, that is, unlimited.
2. maxBuffer -- specify the maximum number of bytes allowed by the stdout stream and stderr stream. If the maximum value is reached, the sub-process will be killed. The default value is 200*1024.
3. killSignal-the ending signal sent to the child process when the timeout or output cache reaches the maximum value. The default value is "SIGTERM", which sends an end signal to the sub-process. This ordered method is usually used to end the process. When SIGTERM signal is used, the process can process or rewrite the default behavior of the signal processor after receiving the signal. If the target process requires it, you can send other signals (such as SIGUSR1) to it at the same time ). You can also choose to send a SIGKILL signal, which will be processed by the operating system and force the sub-process to end immediately. In this way, no cleanup operation will be executed for the sub-process.
If you want to further control the process, you can use the child_process.spawn command, which will be introduced later.
1. evn -- specifies the environment variable passed to the child process. The default value is null. That is, the child process inherits the environment variables of all parent processes before it is created.
NOTE: With the killSignal option, you can send signals to the target process in the form of strings. Signals in Node exist as strings. The following is a list of UNIX signals and corresponding default operations:
You may want to provide a group of extensible parent environment variables for child processes. If you directly modify the process. env object, you will change the environment variables of all modules in the Node process, which will cause a lot of trouble. The alternative is to create a new object and copy all the parameters in process. env. See example 8-2:
LISTING 8-2: execute commands using parameterized environment variables (source code: chapter8/02_env_vars_augment.js)
The Code is as follows:
Var env = process. env,
VarName,
EnvCopy = {},
Exec = require('child_prcess'cmd.exe c;
// Copy process. env to envCopy
For (vaName in ev ){
EnvCopy [varName] = env [varName];
}
// Set some custom Variables
EnvCopy ['customenv var1'] = 'some value ';
EnvCopy ['m m ENV var2'] = 'some other value ';
// Use process. env and custom variables to execute commands
Exec ('LS-la', {env: envCopy}, function (err, stdout, stderr ){
If (err) {throw err ;}
Console. log ('stdout: ', stdout );
Console. log ('stderr: ', stderr );
}
In the preceding example, an envCopy variable is created to save the environment variable. It starts from process. env copies the environment variables of the Node process, adds or replaces some environment variables to be modified, and finally passes envCopy as the environment variable parameter to the exec function and executes External commands.
Remember, environment variables are transmitted between processes through the operating system, and all types of environment variable values reach sub-processes in the form of strings. For example, if the parent process uses the number 123 as an environment variable, the child process receives the value 123 as a string ".
The following example creates two Node scripts in the same directory: parent. js and child. js. The first script will call the second one. Let's create these two files:
LISTING 8-3: environment variable set by the parent process (chapter8/03_environment_number_parent.js)
The Code is as follows:
Var exec = require('child_process'cmd.exe c;
Exec ('node child. js', {env: {number: 123 }}, function (err, stdout, stderr ){
If (err) {throw err ;}
Console. log ('stdout: \ n', stdout );
Console. log ('stderr: \ n', stderr );
});
Save this code to parent. js. below is the source code of the sub-process and save it to child. js (see example 8-4)
Example 8-4: Environment variable for sub-process parsing (chapter8/04_environment_number_child.js)
The Code is as follows:
Var number = process. env. number;
Console. log (typeof (number); // → "string"
Number = parseInt (number, 10 );
Console. log (typeof (number); // → "number"
After saving the file as child. js, you can run the following command in this directory:
The Code is as follows:
$ Node parent. js
The following output is displayed:
The Code is as follows:
Sdtou:
String
Number
Stderr:
As you can see, although the parent process passes a digital environment variable, the child process receives it as a string (see the second line of output ), in the third line, you parse the string into a number.
Generate sub-process
As you can see, you can use the child_process.exe c () function to start an external process and call your callback function at the end of the process. This is easy to use, but it also has some disadvantages:
1. Besides using command line parameters and environment variables, exec () cannot communicate with sub-processes.
2. The output of the sub-process is cached, so you cannot stream it, and it may exhaust the memory.
Fortunately, Node's child_process module allows finer-grained control of sub-processes, including starting, stopping, and other common operations. You can start a new sub-process in the application. Node provides a two-way communication channel that allows the parent process and sub-process to send and receive string data from each other. The parent process can also manage sub-processes, send signals to sub-processes, and forcibly close sub-processes.
Create a sub-process
You can use the child_process.spawn function to create a new sub-process. See example 8-5:
Example 8-5: generate a sub-process. (Chapter8/05_spawning_child.js)
The Code is as follows:
// Import the spawn function of the child_process Module
Var spawn = require ('child _ Process'). spawn;
// Generate a sub-process used to execute the "tail-f/var/log/system. log" command
Var child = spawn ('tail', ['-F','/var/log/system. log']);
The code above generates a sub-process used to execute the tail command and takes "-f" and "/bar/log/system. log" as parameters. The tail command monitors the/var/log/system. og file (if any), and then outputs all newly appended data to the stdout standard output stream. The spawn function returns a ChildProcess object, which is a pointer object and encapsulates the access interfaces of real processes. In this example, we assign this new descriptor to a variable named child.
Listen to data from sub-Processes
Any sub-process handle that contains the stdout attribute will take the standard output stdout of the sub-process as a stream object. You can bind data Events to this stream object so that whenever a data block is available, the corresponding callback function is called. See the following example:
The Code is as follows:
// Print the sub-process output to the console
Child. stdout. on ('data', function (data ){
Console. log ('Tail output: '+ data );
});
When a child process outputs data to stdout, the parent process is notified and the data is printed to the console.
In addition to standard output, the process also has another default output stream: Standard Error stream, which is usually used to output error information.
In this example, if/var/log/system. if the log file does not exist, the tail process will output a message similar to the following: "/var/log/system. log: No such file or directory ". By listening to The stderr stream, the parent process will be notified when this error occurs.
The parent process can listen to standard error streams like this:
The Code is as follows:
Child. stderr. on ('data', function (data ){
Console. log ('Tail error output: ', data );
});
Like stdout, The stderr attribute is also a read-only stream. When a sub-process outputs data to a standard error stream, the parent process will be notified and output data.
Send data to sub-process
In addition to receiving data from the output stream of the child process, the parent process can also write data to the standard input of the child process through the childPoces. stdin attribute to send data to and from the child process.
The sub-process can listen to standard input data through the process. stdin read-only stream, but note that you must first restore the standard input stream (resume) because it is in the paused state by default.
For example, a program with the following features will be created:
1. + 1 Application: a simple application that receives integers from standard input, adds them, and then outputs the added results to the standard output stream. As a simple computing service, this application simulates the Node process as an external service that can execute specific work.
2. Test the client of the + 1 application, send a random integer, and then output the result. It is used to demonstrate how a Node process generates a sub-process and then enables it to execute a specific task.
Use the code 8-6 in the following example to create a file named plus_one.js:
Example 8-6: + 1 Application (chapter8/06_plus_one.js)
The Code is as follows:
// Resume the standard input stream that is paused by default.
Process. stdin. resume ();
Process. stdin. on ('data', function (data ){
Var number;
Try {
// Resolve the input data to an integer
Number = parseInt (data. toString (), 10 );
// + 1
Number + = 1;
// Output result
Process. stdout. write (number + "\ n ");
} Catch (err ){
Process. stderr. write (err. message + "\ n ");
}
});
In the above Code, we wait for the data from the stdin standard input stream. Whenever there is data available, assume it is an integer and resolve it to an integer variable, and then add 1, and output the result to the standard output stream.
Run the program using the following command:
The Code is as follows:
$ Node plus_one.js
After running, the program starts to wait for the input. If you enter an integer and press enter, a number after 1 is displayed on the screen.
You can exit the program by pressing Ctrl-C.
One test Client
Now you need to create a Node process to use the computing service provided by the previous "+ 1 application.
Create a file named plus_one_test.js. For details, see Example 8-7:
Example 8-7: Test + 1 Application (chapter8/07_plus_one_test.js)
The Code is as follows:
Var spawn = require ('child _ Process'). spawn;
// Generate a sub-process to execute + 1 Application
Var child = spawn ('node', ['plus _ one. js']);
// Call the callback function every second
SetInterval (function (){
// Create a random number smaller than 10.000
Var number = Math. floor (Math. random () * 10000 );
// Send that number to the child process:
Child. stdin. write (number + "\ n ");
// Get the response from the child process and print it:
Child. stdout. once ('data', function (data ){
Console. log ('child replied to '+ number +' with: '+ data );
});
},1000 );
Child. stderr. on ('data', function (data ){
Process. stdout. write (data );
});
From Row 1 to row 4, a sub-process is started to run "+ 1 Application", and the setInterval function is used to perform the following operations every second:
1. Create a random number less than 10000
2. Pass this number as a string to the sub-process
3. Wait for the sub-process to reply to a string
4. Because you want to receive only one numeric computing result at a time, you need to use child. stdout. once instead of child. stdout. on. If the latter is used, a data event callback function will be registered every one second. Each registered callback function will be executed when the stdout of the sub-process receives the data, in this way, you will find that the same computing result is output multiple times. This behavior is obviously wrong.
Receive notifications when the sub-process exits
When a sub-process exits, the exit event is triggered. Example 8-8 shows how to listen to it:
Example 8-8: Listen to the exit event of the sub-process (chapter8/09_listen_child_exit.js)
The Code is as follows:
Var spawn = require ('child _ Process'). spawn;
// Generate a sub-process to execute the "ls-la" command
Var child = spawn ('Ls', ['-la']);
Child. stdout. on ('data', function (data ){
Console. log ('data from child: '+ data );
});
// When the sub-process exits:
Child. on ('exit ', function (code ){
Console. log ('child process terminated with Code' + code );
});
The last few lines of code are hacked. The parent process uses the exit event of the child process to listen to its exit event. when the event occurs, the console displays the corresponding output. The exit code of the sub-process is passed to the callback function as the first parameter. Some programs use a non-zero exit code to indicate a certain failure state. For example, if you try to execute the command "ls-al click filename.txt" but the current directory does not have this file, you will get an exit code with a value of 1, as shown in Example 8-9:
Example 8-9: Obtain the exit code of the sub-process (chapter8/10_child_exit_code.js)
The Code is as follows:
Var spawn = require ('child _ Process'). spawn;
// Generate a sub-process and execute the "ls does_not_exist.txt" command
Var child = spawn ('Ls', using 'does_not_exist.txt ']);
// When the sub-process exits
Child. on ('exit ', function (code ){
Console. log ('child process terminated with Code' + code );
});
In this example, the exit event triggers the callback function and passes the exit code of the sub-process to it as the first parameter. If the sub-process exits abnormally due to a signal killing, the corresponding signal code is passed to the callback function as the second parameter, for example, 8-10:
LISTING 8-10: Obtain the exit signal of the sub-process (chapter8/11_child_exit_signal.js)
The Code is as follows:
Var spawn = require ('child _ Process'). spawn;
// Generate a sub-process and run the "sleep 10" command
Var child = spawn ('sleep ', ['10']);
SetTimeout (function (){
Child. kill ();
},1000 );
Child. on ('exit ', function (code, signal ){
If (code ){
Console. log ('child process terminated with Code' + code );
} Else if (signal ){
Console. log ('child process terminated because of signal '+ signal );
}
});
In this example, a sub-process is started to perform sleep for 10 seconds, but a SIGKILL signal is sent to the sub-process in less than 10 seconds, which will result in the following output:
The Code is as follows:
Child process terminated because of signal SIGTERM
Send signals and kill Processes
In this section, you will learn how to use signals to Manage Sub-processes. A signal is a simple way for a parent process to communicate with a child process or even kill a child process.
Different signal codes indicate different meanings and have many signals. The most common of them are used to kill processes. If a process receives a signal that it does not know how to handle, the program will be interrupted by exceptions. Some signals are processed by the process, while others can only be processed by the operating system.
Generally, you can use the child. kill Method to send a signal to the sub-process. The SIGTERM signal is sent by default:
The Code is as follows:
Var spawn = require ('child _ Process'). spawn;
Var child = spawn ('sleep ', ['10']);
SetTimeout (function (){
Child. kill ();
},1000 );
You can also pass in a string that identifies the signal as the unique parameter of the kill method to send a specific signal:
The Code is as follows:
Child. kill ('sigusr2 ');
It should be noted that although the method is named kill, the signal sent does not necessarily kill the sub-process. If the sub-process processes the signal, the default signal behavior will be overwritten. The sub-processes written by Node can rewrite the definition of the signal processor as follows:
The Code is as follows:
Process. on ('sigusr2 ', function (){
Console. log ('Got a SIGUSR2 signal ');
});
Now, you define the signal processor of SIGUSR2. When your process receives the signal of SIGUSR2 again, it will not be killed. Instead, it will output the "Got a SIGUSR2 signal" sentence. With this mechanism, you can design a simple way to communicate with sub-processes or even command it. Although it is not as rich as using the standard input function, this method is much simpler.
Summary
In this chapter, we learned to use the child_process.exe c method to execute External commands. Instead of using command line parameters, we can pass parameters to sub-processes by defining environment variables.
You also learned to call external commands by calling the child_process.spawn method to generate sub-processes. In this way, you can use the input stream and output stream to communicate with sub-processes, or use signals to communicate with sub-processes and kill processes.