spawn()spawn() 的工作原理spawnSync()spawn() 的异步辅助函数exec()execFile()spawnAsync() 的同步辅助函数execSync()execFileSync()'node:child_process' 的函数之间进行选择在本章中,我们将探讨如何通过模块 'node:child_process' 从 Node.js 执行 shell 命令。
模块 'node:child_process' 有一个用于执行 shell 命令(在*生成的*子进程中)的函数,它有两个版本
spawn()。spawnSync()。我们将首先探讨 spawn(),然后是 spawnSync()。最后,我们将查看基于它们并相对相似的以下函数
spawn()exec()execFile()spawnSync()execSync()execFileSync()本章中显示的代码在 Unix 上运行,但我也在 Windows 上对其进行了测试——其中大部分代码只需稍作修改即可运行(例如,使用 '\r\n' 而不是 '\n' 结束行)。
以下功能经常出现在示例中。这就是为什么在这里只解释一次
断言:assert.equal() 用于原始值,assert.deepEqual() 用于对象。示例中从未显示必要的导入
import * as assert from 'node:assert/strict';函数 Readable.toWeb() 将 Node 的原生 stream.Readable 转换为 Web 流(ReadableStream 的实例)。这在 §10 “在 Node.js 上使用 Web 流” 中有解释。Readable 始终在示例中导入。
异步函数 readableStreamToString() 使用可读的 Web 流并返回一个字符串(包装在 Promise 中)。这在 关于 Web 流的章节 中有解释。假设此函数在示例中可用。
spawn()spawn() 的工作原理spawn(
command: string,
args?: Array<string>,
options?: Object
): ChildProcessspawn() 在新进程中异步执行命令:该进程与 Node 的主 JavaScript 进程并发运行,我们可以通过各种方式(通常通过流)与其通信。
接下来,是关于 spawn() 的参数和结果的文档。如果您更喜欢通过示例学习,则可以跳过该内容,继续学习以下小节。
commandcommand 是一个包含 shell 命令的字符串。使用此参数有两种模式
args,command 包含整个 shell 命令。我们甚至可以使用 shell 功能,例如多个可执行文件之间的管道传输、将 I/O 重定向到文件、变量和通配符。options.shell 必须为 true,因为我们需要一个 shell 来处理 shell 功能。command 仅包含命令的名称,args 包含其参数。options.shell 为 true,则会解释参数中的许多元字符,并且通配符和变量名等功能会起作用。options.shell 为 false,则字符串将按字面意思使用,我们永远不必转义元字符。这两种模式将在 本章后面 进行演示。
options以下 options 最有趣
.shell: boolean|string(默认值:false)true。例如,否则无法执行 .bat 和 .cmd 文件。.shell 为 false,则只有核心 shell 功能(例如管道传输、I/O 重定向、文件名通配符和变量)不可用。.shell 为 true,我们必须小心用户输入并对其进行清理,因为很容易执行任意代码。如果我们想将元字符用作非元字符,我们还必须对其进行转义。.shell 设置为 shell 可执行文件的路径。然后,Node.js 使用该可执行文件来执行命令。如果我们将 .shell 设置为 true,则 Node.js 使用'/bin/sh'process.env.ComSpec.cwd: string | URL.stdio: Array<string|Stream>|string.env: Object(默认值:process.env)查看 process.env(例如在 Node.js REPL 中)以查看存在哪些变量。
我们可以使用展开来非破坏性地覆盖现有变量——或者如果它尚不存在则创建它
{env: {...process.env, MY_VAR: 'Hi!'}}.signal: AbortSignalac,我们可以将 ac.signal 传递给 spawn() 并通过 ac.abort() 终止子进程。这将在 本章后面 进行演示。.timeout: number.timeout 毫秒,则将其终止。options.stdio子进程的每个标准 I/O 流都有一个数字 ID,即所谓的*文件描述符*
可能还有更多文件描述符,但这很少见。
options.stdio 配置子进程的流是否以及如何管道传输到父进程中的流。它可以是一个数组,其中每个元素配置与其索引相等的文件描述符。以下值可以用作数组元素
'pipe':
childProcess.stdin 管道传输到子进程的标准输入。请注意,尽管其名称如此,但前者是属于父进程的流。childProcess.stdout。childProcess.stderr。'ignore':忽略子进程的流。
'inherit':将子进程的流管道传输到父进程的相应流。
'inherit'。原生 Node.js 流:管道传输到该流或从该流管道传输。
也支持其他值,但这超出了本章的范围。
除了通过数组指定 options.stdio 之外,我们还可以缩写
'pipe' 等效于 ['pipe', 'pipe', 'pipe'](options.stdio 的默认值)。'ignore' 等效于 ['ignore', 'ignore', 'ignore']。'inherit' 等效于 ['inherit', 'inherit', 'inherit']。ChildProcess 的实例spawn() 返回 ChildProcess 的实例。
有趣的数据属性
.exitCode: number | nullnull 表示进程尚未退出。.signalCode: string | nullnull。有关更多信息,请参阅下方方法 .kill() 的说明。.stdin.stdout.stderr.pid: number | undefined.pid 为 undefined。调用 spawn() 后,此值立即可用。有趣的方法
.kill(signalCode?: number | string = 'SIGTERM'): boolean
向子进程发送 POSIX 信号(通常会导致进程终止)
signal 的手册页 包含一个值列表。SIGINT、SIGTERM 和 SIGKILL。有关更多信息,请参阅 Node.js 文档。此方法将在 本章后面 进行演示。
有趣的事件
.on('exit', (exitCode: number|null, signalCode: string|null) => {})'close' 在子进程退出后所有 stdio 流都关闭时通知我们。.on('error', (err: Error) => {})'exit' 事件。我们将在后面看到 如何将事件转换为可以等待的 Promise。
使用异步 spawn() 时,将异步启动该命令的子进程。以下代码演示了这一点
import {spawn} from 'node:child_process';
spawn(
'echo', ['Command starts'],
{
stdio: 'inherit',
shell: true,
}
);
console.log('After spawn()');这是输出
After spawn()
Command starts
在本节中,我们以两种方式指定相同的命令调用
command 提供整个调用。command 提供命令,并通过第二个参数 args 提供其参数。import {Readable} from 'node:stream';
import {spawn} from 'node:child_process';
const childProcess = spawn(
'echo "Hello, how are you?"',
{
shell: true, // (A)
stdio: ['ignore', 'pipe', 'inherit'], // (B)
}
);
const stdout = Readable.toWeb(
childProcess.stdout.setEncoding('utf-8'));
// Result on Unix
assert.equal(
await readableStreamToString(stdout),
'Hello, how are you?\n' // (C)
);
// Result on Windows: '"Hello, how are you?"\r\n'每个仅使用参数生成命令的命令都需要将 .shell 设置为 true(A 行)– 即使它像这个命令一样简单。
在 B 行,我们告诉 spawn() 如何处理标准 I/O
childProcess.stdout(属于父进程的流)。在这种情况下,我们只对子进程的输出感兴趣。因此,一旦我们处理完输出,我们就完成了。在其他情况下,我们可能必须等到子进程退出。稍后将演示如何做到这一点。
在仅命令模式下,我们会看到更多 shell 的特性 - 例如,Windows 命令 shell 输出包含双引号(最后一行)。
import {Readable} from 'node:stream';
import {spawn} from 'node:child_process';
const childProcess = spawn(
'echo', ['Hello, how are you?'],
{
shell: true,
stdio: ['ignore', 'pipe', 'inherit'],
}
);
const stdout = Readable.toWeb(
childProcess.stdout.setEncoding('utf-8'));
// Result on Unix
assert.equal(
await readableStreamToString(stdout),
'Hello, how are you?\n'
);
// Result on Windows: 'Hello, how are you?\r\n'args 中的元字符让我们探讨一下如果 args 中存在元字符会发生什么
import {Readable} from 'node:stream';
import {spawn} from 'node:child_process';
async function echoUser({shell, args}) {
const childProcess = spawn(
`echo`, args,
{
stdio: ['ignore', 'pipe', 'inherit'],
shell,
}
);
const stdout = Readable.toWeb(
childProcess.stdout.setEncoding('utf-8'));
return readableStreamToString(stdout);
}
// Results on Unix
assert.equal(
await echoUser({shell: false, args: ['$USER']}), // (A)
'$USER\n'
);
assert.equal(
await echoUser({shell: true, args: ['$USER']}), // (B)
'rauschma\n'
);
assert.equal(
await echoUser({shell: true, args: [String.raw`\$USER`]}), // (C)
'$USER\n'
);$))不起作用(A 行)。$USER 被解释为变量(B 行)。其他元字符(例如星号 (*))也会出现类似的效果。
这是 Unix shell 元字符的两个示例。Windows shell 有自己的元字符和自己的转义方式。
让我们使用更多 shell 功能(需要仅命令模式)
import {Readable} from 'node:stream';
import {spawn} from 'node:child_process';
import {EOL} from 'node:os';
const childProcess = spawn(
`(echo cherry && echo apple && echo banana) | sort`,
{
stdio: ['ignore', 'pipe', 'inherit'],
shell: true,
}
);
const stdout = Readable.toWeb(
childProcess.stdout.setEncoding('utf-8'));
assert.equal(
await readableStreamToString(stdout),
'apple\nbanana\ncherry\n'
);到目前为止,我们只读取了子进程的标准输出。但我们也可以将数据发送到标准输入
import {Readable, Writable} from 'node:stream';
import {spawn} from 'node:child_process';
const childProcess = spawn(
`sort`, // (A)
{
stdio: ['pipe', 'pipe', 'inherit'],
}
);
const stdin = Writable.toWeb(childProcess.stdin); // (B)
const writer = stdin.getWriter(); // (C)
try {
await writer.write('Cherry\n');
await writer.write('Apple\n');
await writer.write('Banana\n');
} finally {
writer.close();
}
const stdout = Readable.toWeb(
childProcess.stdout.setEncoding('utf-8'));
assert.equal(
await readableStreamToString(stdout),
'Apple\nBanana\nCherry\n'
);我们使用 shell 命令 sort(A 行)为我们对文本行进行排序。
在 B 行,我们使用 Writable.toWeb() 将原生 Node.js 流转换为 Web 流(有关更多信息,请参阅 §10 “在 Node.js 上使用 Web 流”)。
如何通过写入器写入 WritableStream(C 行)也在 关于 Web 流的章节 中进行了说明。
我们之前让 shell 执行以下命令
(echo cherry && echo apple && echo banana) | sort
在以下示例中,我们手动进行管道连接,从回显(A 行)到排序(B 行)
import {Readable, Writable} from 'node:stream';
import {spawn} from 'node:child_process';
const echo = spawn( // (A)
`echo cherry && echo apple && echo banana`,
{
stdio: ['ignore', 'pipe', 'inherit'],
shell: true,
}
);
const sort = spawn( // (B)
`sort`,
{
stdio: ['pipe', 'pipe', 'inherit'],
shell: true,
}
);
//==== Transferring chunks from echo.stdout to sort.stdin ====
const echoOut = Readable.toWeb(
echo.stdout.setEncoding('utf-8'));
const sortIn = Writable.toWeb(sort.stdin);
const sortInWriter = sortIn.getWriter();
try {
for await (const chunk of echoOut) { // (C)
await sortInWriter.write(chunk);
}
} finally {
sortInWriter.close();
}
//==== Reading sort.stdout ====
const sortOut = Readable.toWeb(
sort.stdout.setEncoding('utf-8'));
assert.equal(
await readableStreamToString(sortOut),
'apple\nbanana\ncherry\n'
);ReadableStreams(例如 echoOut)是异步可迭代的。这就是为什么我们可以使用 for-await-of 循环来读取它们的_块_(流数据的片段)。有关更多信息,请参阅 §10 “在 Node.js 上使用 Web 流”。
主要有三种不成功的退出
以下代码演示了如果无法生成子进程会发生什么。在这种情况下,原因是 shell 的路径没有指向可执行文件(A 行)。
import {spawn} from 'node:child_process';
const childProcess = spawn(
'echo hello',
{
stdio: ['inherit', 'inherit', 'pipe'],
shell: '/bin/does-not-exist', // (A)
}
);
childProcess.on('error', (err) => { // (B)
assert.equal(
err.toString(),
'Error: spawn /bin/does-not-exist ENOENT'
);
});这是我们第一次使用事件来处理子进程。在 B 行,我们为 'error' 事件注册了一个事件侦听器。子进程在当前代码片段完成后启动。这有助于防止竞争条件:当我们开始侦听时,我们可以确定该事件尚未发出。
如果 shell 代码包含错误,我们不会收到 'error' 事件(B 行),我们会收到一个带有非零退出代码的 'exit' 事件(A 行)
import {Readable} from 'node:stream';
import {spawn} from 'node:child_process';
const childProcess = spawn(
'does-not-exist',
{
stdio: ['inherit', 'inherit', 'pipe'],
shell: true,
}
);
childProcess.on('exit',
async (exitCode, signalCode) => { // (A)
assert.equal(exitCode, 127);
assert.equal(signalCode, null);
const stderr = Readable.toWeb(
childProcess.stderr.setEncoding('utf-8'));
assert.equal(
await readableStreamToString(stderr),
'/bin/sh: does-not-exist: command not found\n'
);
}
);
childProcess.on('error', (err) => { // (B)
console.error('We never get here!');
});如果在 Unix 上终止进程,则退出代码为 null(C 行),信号代码为字符串(D 行)
import {Readable} from 'node:stream';
import {spawn} from 'node:child_process';
const childProcess = spawn(
'kill $$', // (A)
{
stdio: ['inherit', 'inherit', 'pipe'],
shell: true,
}
);
console.log(childProcess.pid); // (B)
childProcess.on('exit', async (exitCode, signalCode) => {
assert.equal(exitCode, null); // (C)
assert.equal(signalCode, 'SIGTERM'); // (D)
const stderr = Readable.toWeb(
childProcess.stderr.setEncoding('utf-8'));
assert.equal(
await readableStreamToString(stderr),
'' // (E)
);
});请注意,没有错误输出(E 行)。
我们可以不终止子进程本身(A 行),而是将其暂停更长时间,并通过我们在 B 行记录的进程 ID 手动终止它。
如果我们在 Windows 上终止子进程会发生什么?
exitCode 为 1。signalCode 为 null。有时我们只想等到命令完成。这可以通过事件和 Promise 来实现。
import * as fs from 'node:fs';
import {spawn} from 'node:child_process';
const childProcess = spawn(
`(echo first && echo second) > tmp-file.txt`,
{
shell: true,
stdio: 'inherit',
}
);
childProcess.on('exit', (exitCode, signalCode) => { // (A)
assert.equal(exitCode, 0);
assert.equal(signalCode, null);
assert.equal(
fs.readFileSync('tmp-file.txt', {encoding: 'utf-8'}),
'first\nsecond\n'
);
});我们正在使用标准的 Node.js 事件模式,并为 'exit' 事件注册一个侦听器(A 行)。
import * as fs from 'node:fs';
import {spawn} from 'node:child_process';
const childProcess = spawn(
`(echo first && echo second) > tmp-file.txt`,
{
shell: true,
stdio: 'inherit',
}
);
const {exitCode, signalCode} = await onExit(childProcess); // (A)
assert.equal(exitCode, 0);
assert.equal(signalCode, null);
assert.equal(
fs.readFileSync('tmp-file.txt', {encoding: 'utf-8'}),
'first\nsecond\n'
);我们在 A 行使用的辅助函数 onExit() 返回一个 Promise,如果发出 'exit' 事件,则该 Promise 将被兑现
export function onExit(eventEmitter) {
return new Promise((resolve, reject) => {
eventEmitter.once('exit', (exitCode, signalCode) => {
if (exitCode === 0) { // (B)
resolve({exitCode, signalCode});
} else {
reject(new Error(
`Non-zero exit: code ${exitCode}, signal ${signalCode}`));
}
});
eventEmitter.once('error', (err) => { // (C)
reject(err);
});
});
}如果 eventEmitter 失败,则返回的 Promise 将被拒绝,并且 await 在 A 行抛出异常。onExit() 处理两种类型的故障
exitCode 不为零(B 行)。这发生在exitCode 大于零。exitCode 为 null,而 signalCode 为非空。'error' 事件(C 行)。如果无法生成子进程,则会发生这种情况。在此示例中,我们使用 AbortController 来终止 shell 命令
import {spawn} from 'node:child_process';
const abortController = new AbortController(); // (A)
const childProcess = spawn(
`echo Hello`,
{
stdio: 'inherit',
shell: true,
signal: abortController.signal, // (B)
}
);
childProcess.on('error', (err) => {
assert.equal(
err.toString(),
'AbortError: The operation was aborted'
);
});
abortController.abort(); // (C)我们创建一个 AbortController(A 行),将其信号传递给 spawn()(B 行),并通过 AbortController 终止 shell 命令(C 行)。
子进程异步启动(在当前代码片段执行之后)。这就是为什么我们可以在进程甚至启动之前就中止它,以及为什么在这种情况下我们看不到任何输出的原因。
.kill() 终止子进程在下一个示例中,我们通过 .kill() 方法终止子进程(最后一行)
import {spawn} from 'node:child_process';
const childProcess = spawn(
`echo Hello`,
{
stdio: 'inherit',
shell: true,
}
);
childProcess.on('exit', (exitCode, signalCode) => {
assert.equal(exitCode, null);
assert.equal(signalCode, 'SIGTERM');
});
childProcess.kill(); // default argument value: 'SIGTERM'再一次,我们在子进程启动之前(异步!)就终止了它,并且没有输出。
spawnSync()spawnSync(
command: string,
args?: Array<string>,
options?: Object
): ObjectspawnSync() 是 spawn() 的同步版本 - 它会等待子进程退出,然后同步 (!) 返回一个对象。
参数与 spawn() 的参数 大致相同。options 有一些额外的属性 - 例如
.input: string | TypedArray | DataView.encoding: string(默认值:'buffer')该函数返回一个对象。它最有趣的属性是
.stdout: Buffer | string.stderr: Buffer | string.status: number | nullnull。退出代码和信号代码其中之一为非空。.signal: string | nullnull。退出代码和信号代码其中之一为非空。.error?: Error使用异步 spawn() 时,子进程并发运行,我们可以通过流读取标准 I/O。相反,同步 spawnSync() 会收集流的内容并将它们同步返回给我们(请参阅下一小节)。
使用同步 spawnSync() 时,将同步启动该命令的子进程。以下代码演示了这一点
import {spawnSync} from 'node:child_process';
spawnSync(
'echo', ['Command starts'],
{
stdio: 'inherit',
shell: true,
}
);
console.log('After spawnSync()');这是输出
Command starts
After spawnSync()
以下代码演示了如何读取标准输出
import {spawnSync} from 'node:child_process';
const result = spawnSync(
`echo rock && echo paper && echo scissors`,
{
stdio: ['ignore', 'pipe', 'inherit'], // (A)
encoding: 'utf-8', // (B)
shell: true,
}
);
console.log(result);
assert.equal(
result.stdout, // (C)
'rock\npaper\nscissors\n'
);
assert.equal(result.stderr, null); // (D)在 A 行,我们使用 options.stdio 告诉 spawnSync() 我们只对标准输出感兴趣。我们忽略标准输入并将标准错误管道连接到父进程。
因此,我们只获得标准输出的结果属性(C 行),而标准错误的属性为 null(D 行)。
由于我们无法访问 spawnSync() 在内部用于处理子进程的标准 I/O 的流,因此我们通过 options.encoding 告诉它使用哪种编码(B 行)。
我们可以通过选项属性 .input 将数据发送到子进程的标准输入流(A 行)
import {spawnSync} from 'node:child_process';
const result = spawnSync(
`sort`,
{
stdio: ['pipe', 'pipe', 'inherit'],
encoding: 'utf-8',
input: 'Cherry\nApple\nBanana\n', // (A)
}
);
assert.equal(
result.stdout,
'Apple\nBanana\nCherry\n'
);主要有三种不成功的退出(当退出代码不为零时)
如果生成失败,spawn() 会发出 'error' 事件。相反,spawnSync() 会将 result.error 设置为错误对象
import {spawnSync} from 'node:child_process';
const result = spawnSync(
'echo hello',
{
stdio: ['ignore', 'inherit', 'pipe'],
encoding: 'utf-8',
shell: '/bin/does-not-exist',
}
);
assert.equal(
result.error.toString(),
'Error: spawnSync /bin/does-not-exist ENOENT'
);如果 shell 中发生错误,则退出代码 result.status 大于零,而 result.signal 为 null
import {spawnSync} from 'node:child_process';
const result = spawnSync(
'does-not-exist',
{
stdio: ['ignore', 'inherit', 'pipe'],
encoding: 'utf-8',
shell: true,
}
);
assert.equal(result.status, 127);
assert.equal(result.signal, null);
assert.equal(
result.stderr, '/bin/sh: does-not-exist: command not found\n'
);如果子进程在 Unix 上被终止,则 result.signal 包含信号的名称,而 result.status 为 null
import {spawnSync} from 'node:child_process';
const result = spawnSync(
'kill $$',
{
stdio: ['ignore', 'inherit', 'pipe'],
encoding: 'utf-8',
shell: true,
}
);
assert.equal(result.status, null);
assert.equal(result.signal, 'SIGTERM');
assert.equal(result.stderr, ''); // (A)请注意,没有输出发送到标准错误流(A 行)。
如果我们在 Windows 上终止子进程
result.status 为 1result.signal 为 nullresult.stderr 为 ''spawn() 的异步辅助函数在本节中,我们将介绍模块 node:child_process 中基于 spawn() 的两个异步函数
exec()execFile()我们在本章中忽略 fork()。引用 Node.js 文档
fork()生成一个新的 Node.js 进程,并使用已建立的 IPC 通信通道调用指定的模块,该通道允许在父进程和子进程之间发送消息。
exec()exec(
command: string,
options?: Object,
callback?: (error, stdout, stderr) => void
): ChildProcessexec() 在新生成的 shell 中运行命令。与 spawn() 的主要区别在于
exec() 还通过回调传递结果:错误对象或标准输出和标准错误的内容。spawn() 仅在无法生成子进程时才发出 'error' 事件。其他两种故障通过退出代码和(在 Unix 上)信号代码来处理。args。options.shell 的默认值为 true。import {exec} from 'node:child_process';
const childProcess = exec(
'echo Hello',
(error, stdout, stderr) => {
if (error) {
console.error('error: ' + error.toString());
return;
}
console.log('stdout: ' + stdout); // 'stdout: Hello\n'
console.error('stderr: ' + stderr); // 'stderr: '
}
);exec() 可以通过 util.promisify() 转换为基于 Promise 的函数
{stdout, stderr}error 相同的值,但有两个附加属性:.stdout 和 .stderr。import * as util from 'node:util';
import * as child_process from 'node:child_process';
const execAsync = util.promisify(child_process.exec);
try {
const resultPromise = execAsync('echo Hello');
const {childProcess} = resultPromise;
const obj = await resultPromise;
console.log(obj); // { stdout: 'Hello\n', stderr: '' }
} catch (err) {
console.error(err);
}execFile()execFile(file, args?, options?, callback?): ChildProcess
工作方式与 exec() 类似,但有以下区别
args。options.shell 的默认值为 false。与 exec() 一样,execFile() 可以通过 util.promisify() 转换为基于 Promise 的函数。
spawnAsync() 的同步辅助函数execSync()execSync(
command: string,
options?: Object
): Buffer | stringexecSync() 在新的子进程中运行命令,并同步等待该进程退出。与 spawnSync() 的主要区别在于
spawnSync() 的结果仅在无法生成子进程时才具有 .error 属性。其他两种故障通过退出代码和(在 Unix 上)信号代码来处理。args。options.shell 的默认值为 true。import {execSync} from 'node:child_process';
try {
const stdout = execSync('echo Hello');
console.log('stdout: ' + stdout); // 'stdout: Hello\n'
} catch (err) {
console.error('Error: ' + err.toString());
}execFileSync()execFileSync(file, args?, options?): Buffer | string
工作方式与 execSync() 类似,但有以下区别
args。options.shell 的默认值为 false。Anton Medvedev 开发的 tinysh 是一个小型库,可以帮助生成 shell 命令,例如:
import sh from 'tinysh';
console.log(sh.ls('-l'));
console.log(sh.cat('README.md'));我们可以通过使用 .call() 传递一个对象作为 this 来覆盖默认选项
sh.tee.call({input: 'Hello, world!'}, 'file.txt');我们可以使用任何属性名称,tinysh 会使用该名称执行 shell 命令。它是通过 代理 实现的。这是实际库的略微修改版本
import {execFileSync} from 'node:child_process';
const sh = new Proxy({}, {
get: (_, bin) => function (...args) { // (A)
return execFileSync(bin, args,
{
encoding: 'utf-8',
shell: true,
...this // (B)
}
);
},
});在 A 行中,我们可以看到,如果我们从 sh 中获取一个名称为 bin 的属性,则会返回一个函数,该函数调用 execFileSync() 并使用 bin 作为第一个参数。
在 B 行中展开 this 使我们能够通过 .call() 指定选项。默认值排在第一位,以便可以通过 this 覆盖它们。
在 Windows 上使用 node-powershell 库 如下所示
import { PowerShell } from 'node-powershell';
PowerShell.$`echo "hello from PowerShell"`;'node:child_process' 的函数之间进行选择一般约束
spawn() 更简单,因为它没有用于传递错误和标准 I/O 内容的回调。exec() 和 execFile()spawnSync()、execSync()、execFileSync()异步函数 - 在 spawn() 和 exec() 或 execFile() 之间进行选择
exec() 和 execFile() 有两个优点spawn()。它的签名更简单,没有(可选的)回调。同步函数 - 在 spawnSync() 和 execSync() 或 execFileSync() 之间进行选择
execSync() 和 execFileSync() 有两个特点execSync() 和 execFileSync() 通过其返回值和异常提供的更多信息,请选择 spawnSync()。在 exec() 和 execFile() 之间进行选择(相同的论点也适用于在 execSync() 和 execFileSync() 之间进行选择)
options.shell 的默认值在 exec() 中为 true,但在 execFile() 中为 false。execFile() 支持 args,而 exec() 不支持。