Перейти к содержанию

Child процессы

latest

Стабильность: 2 – Стабильная

АПИ является удовлетворительным. Совместимость с NPM имеет высший приоритет и не будет нарушена кроме случаев явной необходимости.

Модуль node:child_process предоставляет возможность порождать подпроцессы способом, который похож, но не идентичен popen(3). В основном эта возможность реализуется функцией child_process.spawn():

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
const { spawn } = require('node:child_process');
const ls = spawn('ls', ['-lh', '/usr']);

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

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

ls.on('close', (code) => {
  console.log(`child process exited with code ${code}`);
});
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import { spawn } from 'node:child_process';
import { once } from 'node:events';
const ls = spawn('ls', ['-lh', '/usr']);

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

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

const [code] = await once(ls, 'close');
console.log(`child process exited with code ${code}`);

По умолчанию между родительским процессом Node.js и порождённым подпроцессом устанавливаются каналы для stdin, stdout и stderr. У этих каналов ограниченная (и зависящая от платформы) пропускная способность. Если подпроцесс пишет в stdout больше этого лимита без перехвата вывода, подпроцесс блокируется, ожидая, пока буфер канала примет больше данных. Это совпадает с поведением каналов в оболочке. Используйте опцию { stdio: 'ignore' }, если вывод не будет потребляться.

Поиск команды выполняется с использованием переменной окружения options.env.PATH, если в объекте options есть env. В противном случае используется process.env.PATH. Если options.env задан без PATH, на Unix поиск выполняется по пути по умолчанию /usr/bin:/bin (см. руководство вашей ОС по execvpe/execvp), на Windows используется переменная окружения PATH текущих процессов.

В Windows переменные окружения не чувствительны к регистру. Node.js лексикографически сортирует ключи env и использует первый, который совпадает без учёта регистра. В подпроцесс передаётся только первая (в лексикографическом порядке) запись. Это может вызывать проблемы в Windows при передаче в опцию env объектов с несколькими вариантами одного и того же ключа, например PATH и Path.

Метод child_process.spawn() порождает дочерний процесс асинхронно, не блокируя цикл событий Node.js. Функция child_process.spawnSync() предоставляет эквивалентную функциональность синхронно, блокируя цикл событий, пока порождённый процесс не завершится или не будет принудительно остановлен.

Для удобства модуль node:child_process предоставляет несколько синхронных и асинхронных альтернатив child_process.spawn() и child_process.spawnSync(). Каждая из этих альтернатив реализована поверх child_process.spawn() или child_process.spawnSync().

  • child_process.exec(): порождает оболочку и выполняет команду внутри неё, передавая stdout и stderr в функцию обратного вызова по завершении.
  • child_process.execFile(): похож на child_process.exec(), но по умолчанию запускает команду напрямую, без предварительного порождения оболочки.
  • child_process.fork(): порождает новый процесс Node.js и вызывает указанный модуль с установленным каналом IPC, позволяющим обмениваться сообщениями между родителем и дочерним процессом.
  • child_process.execSync(): синхронная версия child_process.exec(), блокирующая цикл событий Node.js.
  • child_process.execFileSync(): синхронная версия child_process.execFile(), блокирующая цикл событий Node.js.

В некоторых сценариях, например при автоматизации сценариев оболочки, синхронные аналоги могут быть удобнее. Однако во многих случаях синхронные методы существенно снижают производительность из‑за остановки цикла событий на время завершения порождённых процессов.

Асинхронное создание процессов

Методы child_process.spawn(), child_process.fork(), child_process.exec() и child_process.execFile() следуют идиоматической схеме асинхронного программирования, характерной для других API Node.js.

Каждый из методов возвращает экземпляр ChildProcess. Эти объекты реализуют API EventEmitter в Node.js и позволяют родительскому процессу регистрировать обработчики, вызываемые при определённых событиях в жизненном цикле дочернего процесса.

Методы child_process.exec() и child_process.execFile() дополнительно позволяют указать необязательную функцию callback, вызываемую при завершении дочернего процесса.

Запуск файлов .bat и .cmd в Windows

Важность различия между child_process.exec() и child_process.execFile() может зависеть от платформы. В Unix-совместимых ОС (Unix, Linux, macOS) child_process.execFile() может быть эффективнее, так как по умолчанию не порождает оболочку. В Windows файлы .bat и .cmd не исполняются сами по себе без терминала и поэтому не могут быть запущены через child_process.execFile(). В Windows файлы .bat и .cmd можно вызвать так:

  • используя child_process.spawn() с установленной опцией shell (не рекомендуется, см. DEP0190), или
  • используя child_process.exec(), или
  • запустив cmd.exe и передав файл .bat или .cmd аргументом (как внутренне делает child_process.exec()).

В любом случае, если имя файла скрипта содержит пробелы, его нужно заключить в кавычки.

1
2
3
4
5
6
7
8
9
const { exec, spawn } = require('node:child_process');

exec('my.bat', (err, stdout, stderr) => { /* ... */ });

// Или прямой запуск cmd.exe:
const bat = spawn('cmd.exe', ['/c', 'my.bat']);

// Если имя файла скрипта содержит пробелы, его нужно заключить в кавычки
exec('"my script.cmd" a b', (err, stdout, stderr) => { /* ... */ });
1
2
3
4
5
6
7
8
9
import { exec, spawn } from 'node:child_process';

exec('my.bat', (err, stdout, stderr) => { /* ... */ });

// Или прямой запуск cmd.exe:
const bat = spawn('cmd.exe', ['/c', 'my.bat']);

// Если имя файла скрипта содержит пробелы, его нужно заключить в кавычки
exec('"my script.cmd" a b', (err, stdout, stderr) => { /* ... */ });

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

  • command <string> Команда для выполнения с аргументами, разделёнными пробелами.
  • options <Object>
    • cwd <string> | <URL> Текущий рабочий каталог дочернего процесса. По умолчанию: process.cwd().
    • env <Object> Пары «ключ–значение» окружения. По умолчанию: process.env.
    • encoding <string> По умолчанию: 'utf8'
    • shell <string> Оболочка для выполнения команды. См. требования к оболочке и оболочку Windows по умолчанию. По умолчанию: '/bin/sh' на Unix, process.env.ComSpec в Windows.
    • signal <AbortSignal> позволяет прервать дочерний процесс с помощью AbortSignal.
    • timeout <number> По умолчанию: 0
    • maxBuffer <number> Максимальный объём данных в байтах, допустимый в stdout или stderr. При превышении дочерний процесс завершается, вывод обрезается. См. оговорку в разделе о maxBuffer и Unicode. По умолчанию: 1024 * 1024.
    • killSignal <string> | <integer> По умолчанию: 'SIGTERM'
    • uid <number> Задаёт идентификатор пользователя процесса (см. setuid(2)).
    • gid <number> Задаёт идентификатор группы процесса (см. setgid(2)).
    • windowsHide <boolean> Скрыть консольное окно подпроцесса, которое обычно создаётся в Windows. По умолчанию: false.
  • callback <Function> вызывается с выводом при завершении процесса.
  • Возвращает: <ChildProcess>

Порождает оболочку и выполняет в ней command, буферизуя сгенерированный вывод. Строка command, передаваемая в exec, обрабатывается оболочкой напрямую; специальные символы (зависят от оболочки) нужно учитывать соответствующим образом:

1
2
3
4
5
6
7
8
const { exec } = require('node:child_process');

exec('"/path/to/test file/test.sh" arg1 arg2');
// Двойные кавычки, чтобы пробел в пути не воспринимался как
// разделитель нескольких аргументов.

exec('echo "The \\$HOME variable is $HOME"');
// Переменная $HOME экранирована в первом случае, но не во втором.
1
2
3
4
5
6
7
8
import { exec } from 'node:child_process';

exec('"/path/to/test file/test.sh" arg1 arg2');
// Двойные кавычки, чтобы пробел в пути не воспринимался как
// разделитель нескольких аргументов.

exec('echo "The \\$HOME variable is $HOME"');
// Переменная $HOME экранирована в первом случае, но не во втором.

Никогда не передавайте в эту функцию несанированный пользовательский ввод. Любой ввод с метасимволами оболочки может привести к выполнению произвольной команды.

Если указана функция callback, она вызывается с аргументами (error, stdout, stderr). При успехе error будет null. При ошибке error будет экземпляром Error. Свойство error.code — код выхода процесса. По соглашению любой код выхода, отличный от 0, означает ошибку. error.signal — сигнал, которым завершили процесс.

Аргументы stdout и stderr, передаваемые в callback, содержат вывод stdout и stderr дочернего процесса. По умолчанию Node.js декодирует вывод как UTF-8 и передаёт строки в callback. Опция encoding задаёт кодировку для декодирования stdout и stderr. Если encoding равен 'buffer' или нераспознанной кодировке, в callback передаются объекты Buffer.

1
2
3
4
5
6
7
8
9
const { exec } = require('node:child_process');
exec('cat *.js missing_file | wc -l', (error, stdout, stderr) => {
  if (error) {
    console.error(`exec error: ${error}`);
    return;
  }
  console.log(`stdout: ${stdout}`);
  console.error(`stderr: ${stderr}`);
});
1
2
3
4
5
6
7
8
9
import { exec } from 'node:child_process';
exec('cat *.js missing_file | wc -l', (error, stdout, stderr) => {
  if (error) {
    console.error(`exec error: ${error}`);
    return;
  }
  console.log(`stdout: ${stdout}`);
  console.error(`stderr: ${stderr}`);
});

Если timeout больше 0, родительский процесс отправит сигнал, заданный свойством killSignal (по умолчанию 'SIGTERM'), если дочерний процесс работает дольше timeout миллисекунд.

В отличие от системного вызова exec(3) POSIX, child_process.exec() не заменяет текущий процесс и выполняет команду через оболочку.

Если метод вызывается в варианте с util.promisify(), он возвращает Promise на объект с полями stdout и stderr. Экземпляр ChildProcess прикреплён к Promise как свойство child. При ошибке (включая ненулевой код выхода) промис отклоняется с тем же объектом error, что и в callback, плюс дополнительные свойства stdout и stderr.

1
2
3
4
5
6
7
8
9
const util = require('node:util');
const exec = util.promisify(require('node:child_process').exec);

async function lsExample() {
  const { stdout, stderr } = await exec('ls');
  console.log('stdout:', stdout);
  console.error('stderr:', stderr);
}
lsExample();
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import { promisify } from 'node:util';
import child_process from 'node:child_process';
const exec = promisify(child_process.exec);

async function lsExample() {
  const { stdout, stderr } = await exec('ls');
  console.log('stdout:', stdout);
  console.error('stderr:', stderr);
}
lsExample();

Если включена опция signal, вызов .abort() на соответствующем AbortController аналогичен вызову .kill() у дочернего процесса, но в callback передаётся AbortError:

1
2
3
4
5
6
7
const { exec } = require('node:child_process');
const controller = new AbortController();
const { signal } = controller;
const child = exec('grep ssh', { signal }, (error) => {
  console.error(error); // AbortError
});
controller.abort();
1
2
3
4
5
6
7
import { exec } from 'node:child_process';
const controller = new AbortController();
const { signal } = controller;
const child = exec('grep ssh', { signal }, (error) => {
  console.error(error); // AbortError
});
controller.abort();

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

  • file <string> Имя или путь исполняемого файла.
  • args <string[]> Список строковых аргументов.
  • options <Object>
    • cwd <string> | <URL> Текущий рабочий каталог дочернего процесса.
    • env <Object> Пары «ключ–значение» окружения. По умолчанию: process.env.
    • encoding <string> По умолчанию: 'utf8'
    • timeout <number> По умолчанию: 0
    • maxBuffer <number> Максимальный объём данных в байтах для stdout или stderr. При превышении дочерний процесс завершается, вывод обрезается. См. оговорку в разделе о maxBuffer и Unicode. По умолчанию: 1024 * 1024.
    • killSignal <string> | <integer> По умолчанию: 'SIGTERM'
    • uid <number> Задаёт идентификатор пользователя процесса (см. setuid(2)).
    • gid <number> Задаёт идентификатор группы процесса (см. setgid(2)).
    • windowsHide <boolean> Скрыть консольное окно подпроцесса в Windows. По умолчанию: false.
    • windowsVerbatimArguments <boolean> В Windows аргументы не экранируются и не заключаются в кавычки. На Unix игнорируется. По умолчанию: false.
    • shell <boolean> | <string> Если true, запускает command в оболочке. На Unix — '/bin/sh', в Windows — process.env.ComSpec. Можно указать другую оболочку строкой. См. требования к оболочке и оболочку Windows по умолчанию. По умолчанию: false (без оболочки).
    • signal <AbortSignal> позволяет прервать дочерний процесс через AbortSignal.
  • callback <Function> вызывается с выводом при завершении процесса.
  • Возвращает: <ChildProcess>

Функция child_process.execFile() похожа на child_process.exec(), но по умолчанию не порождает оболочку: указанный исполняемый file запускается напрямую как новый процесс, что немного эффективнее, чем child_process.exec().

Поддерживаются те же опции, что и у child_process.exec(). Поскольку оболочка не запускается, перенаправление ввода-вывода и подстановка имён файлов (globbing) недоступны.

1
2
3
4
5
6
7
const { execFile } = require('node:child_process');
const child = execFile('node', ['--version'], (error, stdout, stderr) => {
  if (error) {
    throw error;
  }
  console.log(stdout);
});
1
2
3
4
5
6
7
import { execFile } from 'node:child_process';
const child = execFile('node', ['--version'], (error, stdout, stderr) => {
  if (error) {
    throw error;
  }
  console.log(stdout);
});

Аргументы stdout и stderr в callback содержат вывод дочернего процесса. По умолчанию Node.js декодирует вывод как UTF-8 и передаёт строки. Опция encoding задаёт кодировку для декодирования stdout и stderr. Если encoding равен 'buffer' или нераспознанной кодировке, в callback передаются объекты Buffer.

В варианте с util.promisify() метод возвращает Promise на объект с полями stdout и stderr; экземпляр ChildProcess доступен как child у промиса. При ошибке (включая ненулевой код выхода) промис отклоняется с тем же error, что в callback, плюс свойства stdout и stderr.

1
2
3
4
5
6
7
const util = require('node:util');
const execFile = util.promisify(require('node:child_process').execFile);
async function getVersion() {
  const { stdout } = await execFile('node', ['--version']);
  console.log(stdout);
}
getVersion();
1
2
3
4
5
6
7
8
import { promisify } from 'node:util';
import child_process from 'node:child_process';
const execFile = promisify(child_process.execFile);
async function getVersion() {
  const { stdout } = await execFile('node', ['--version']);
  console.log(stdout);
}
getVersion();

Если включена опция shell, не передавайте в эту функцию несанированный пользовательский ввод. Метасимволы оболочки могут привести к выполнению произвольной команды.

Если включена опция signal, вызов .abort() на соответствующем AbortController аналогичен .kill() у дочернего процесса, но в callback передаётся AbortError:

1
2
3
4
5
6
7
const { execFile } = require('node:child_process');
const controller = new AbortController();
const { signal } = controller;
const child = execFile('node', ['--version'], { signal }, (error) => {
  console.error(error); // AbortError
});
controller.abort();
1
2
3
4
5
6
7
import { execFile } from 'node:child_process';
const controller = new AbortController();
const { signal } = controller;
const child = execFile('node', ['--version'], { signal }, (error) => {
  console.error(error); // AbortError
});
controller.abort();

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

  • modulePath <string> | <URL> Модуль для запуска в дочернем процессе.
  • args <string[]> Список строковых аргументов.
  • options <Object>
    • cwd <string> | <URL> Текущий рабочий каталог дочернего процесса.
    • detached <boolean> Подготовить дочерний процесс к работе независимо от родителя. Поведение зависит от платформы (см. options.detached).
    • env <Object> Пары «ключ–значение» окружения. По умолчанию: process.env.
    • execPath <string> Исполняемый файл для создания дочернего процесса.
    • execArgv <string[]> Аргументы, передаваемые исполняемому файлу. По умолчанию: process.execArgv.
    • gid <number> Задаёт идентификатор группы процесса (см. setgid(2)).
    • serialization <string> Вид сериализации сообщений между процессами: 'json' или 'advanced'. Подробнее — расширенная сериализация. По умолчанию: 'json'.
    • signal <AbortSignal> позволяет закрыть дочерний процесс через AbortSignal.
    • killSignal <string> | <integer> Сигнал при завершении по таймауту или abort. По умолчанию: 'SIGTERM'.
    • silent <boolean> Если true, stdin, stdout и stderr дочернего процесса направляются в родитель; иначе наследуются от родителя — см. варианты 'pipe' и 'inherit' у stdio в child_process.spawn(). По умолчанию: false.
    • stdio <Array> | <string> См. stdio у child_process.spawn(). При указании этой опции она перекрывает silent. В массиве должен быть ровно один элемент 'ipc', иначе будет ошибка. Например [0, 1, 2, 'ipc'].
    • uid <number> Задаёт идентификатор пользователя процесса (см. setuid(2)).
    • windowsVerbatimArguments <boolean> В Windows аргументы не экранируются. На Unix игнорируется. По умолчанию: false.
    • timeout <number> Максимальное время работы процесса в миллисекундах. По умолчанию: undefined.
  • Возвращает: <ChildProcess>

Метод child_process.fork() — частный случай child_process.spawn() для запуска новых процессов Node.js. Как и child_process.spawn(), возвращает ChildProcess с дополнительным встроенным каналом обмена сообщениями между родителем и потомком. Подробнее — subprocess.send().

Порождённые процессы Node.js независимы от родителя, кроме установленного канала IPC. У каждого процесса своя память и свой экземпляр V8. Из‑за накладных расходов не рекомендуется порождать очень много дочерних процессов Node.js.

По умолчанию child_process.fork() запускает Node.js с process.execPath родителя. Свойство execPath в options задаёт другой путь к исполняемому файлу.

Процессы Node.js с пользовательским execPath обмениваются с родителем через дескриптор файла (fd), указанный в дочернем процессе переменной окружения NODE_CHANNEL_FD.

В отличие от системного вызова fork(2) POSIX, child_process.fork() не клонирует текущий процесс.

Опция shell из child_process.spawn() для child_process.fork() не поддерживается и игнорируется, если задана.

Если включена опция signal, вызов .abort() на соответствующем AbortController аналогичен .kill() у дочернего процесса, но в callback передаётся AbortError:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
const { fork } = require('node:child_process');
const process = require('node:process');

if (process.argv[2] === 'child') {
  setTimeout(() => {
    console.log(`Hello from ${process.argv[2]}!`);
  }, 1_000);
} else {
  const controller = new AbortController();
  const { signal } = controller;
  const child = fork(__filename, ['child'], { signal });
  child.on('error', (err) => {
    // Будет вызвано с err типа AbortError при abort контроллера
  });
  controller.abort(); // Останавливает дочерний процесс
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import { fork } from 'node:child_process';
import process from 'node:process';

if (process.argv[2] === 'child') {
  setTimeout(() => {
    console.log(`Hello from ${process.argv[2]}!`);
  }, 1_000);
} else {
  const controller = new AbortController();
  const { signal } = controller;
  const child = fork(import.meta.url, ['child'], { signal });
  child.on('error', (err) => {
    // Будет вызвано с err типа AbortError при abort контроллера
  });
  controller.abort(); // Останавливает дочерний процесс
}

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

  • command <string> Команда для запуска.
  • args <string[]> Список строковых аргументов.
  • options <Object>
    • cwd <string> | <URL> Текущий рабочий каталог дочернего процесса.
    • env <Object> Пары «ключ–значение» окружения. По умолчанию: process.env.
    • argv0 <string> Явно задаёт argv[0] для дочернего процесса; если не указано — используется command.
    • stdio <Array> | <string> Конфигурация stdio дочернего процесса (см. options.stdio).
    • detached <boolean> Подготовить дочерний процесс к работе независимо от родителя. Поведение зависит от платформы (см. options.detached).
    • uid <number> Задаёт идентификатор пользователя процесса (см. setuid(2)).
    • gid <number> Задаёт идентификатор группы процесса (см. setgid(2)).
    • serialization <string> Вид сериализации сообщений: 'json' или 'advanced'. Подробнее — расширенная сериализация. По умолчанию: 'json'.
    • shell <boolean> | <string> Если true, запускает command в оболочке. На Unix — '/bin/sh', в Windows — process.env.ComSpec. Можно указать другую оболочку. См. требования к оболочке и оболочку Windows по умолчанию. По умолчанию: false (без оболочки).
    • windowsVerbatimArguments <boolean> В Windows аргументы не экранируются. На Unix игнорируется. Автоматически true, если задан shell и это CMD. По умолчанию: false.
    • windowsHide <boolean> Скрыть консольное окно подпроцесса в Windows. По умолчанию: false.
    • signal <AbortSignal> позволяет прервать дочерний процесс через AbortSignal.
    • timeout <number> Максимальное время работы процесса в миллисекундах. По умолчанию: undefined.
    • killSignal <string> | <integer> Сигнал при завершении по таймауту или abort. По умолчанию: 'SIGTERM'.
  • Возвращает: <ChildProcess>

Метод child_process.spawn() запускает новый процесс с командой command и аргументами командной строки в args. Если args опущен, по умолчанию используется пустой массив.

Если включена опция shell, не передавайте несанированный пользовательский ввод. Метасимволы оболочки могут привести к выполнению произвольной команды.

Третий аргумент задаёт дополнительные опции; значения по умолчанию:

1
2
3
4
const defaults = {
    cwd: undefined,
    env: process.env,
};

cwd задаёт рабочий каталог, из которого запускается процесс. Если не указан, наследуется текущий рабочий каталог. Если путь не существует, дочерний процесс генерирует ошибку ENOENT и сразу завершается. ENOENT также возникает, если команда не найдена.

env задаёт переменные окружения, видимые новому процессу; по умолчанию — process.env.

Значения undefined в env игнорируются.

Пример запуска ls -lh /usr с перехватом stdout, stderr и кода выхода:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
const { spawn } = require('node:child_process');
const ls = spawn('ls', ['-lh', '/usr']);

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

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

ls.on('close', (code) => {
  console.log(`child process exited with code ${code}`);
});
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import { spawn } from 'node:child_process';
import { once } from 'node:events';
const ls = spawn('ls', ['-lh', '/usr']);

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

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

const [code] = await once(ls, 'close');
console.log(`child process exited with code ${code}`);

Пример: обходной способ выполнить ps ax | grep ssh

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
const { spawn } = require('node:child_process');
const ps = spawn('ps', ['ax']);
const grep = spawn('grep', ['ssh']);

ps.stdout.on('data', (data) => {
  grep.stdin.write(data);
});

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

ps.on('close', (code) => {
  if (code !== 0) {
    console.log(`ps process exited with code ${code}`);
  }
  grep.stdin.end();
});

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

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

grep.on('close', (code) => {
  if (code !== 0) {
    console.log(`grep process exited with code ${code}`);
  }
});
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import { spawn } from 'node:child_process';
const ps = spawn('ps', ['ax']);
const grep = spawn('grep', ['ssh']);

ps.stdout.on('data', (data) => {
  grep.stdin.write(data);
});

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

ps.on('close', (code) => {
  if (code !== 0) {
    console.log(`ps process exited with code ${code}`);
  }
  grep.stdin.end();
});

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

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

grep.on('close', (code) => {
  if (code !== 0) {
    console.log(`grep process exited with code ${code}`);
  }
});

Пример проверки неудачного spawn:

1
2
3
4
5
6
const { spawn } = require('node:child_process');
const subprocess = spawn('bad_command');

subprocess.on('error', (err) => {
  console.error('Не удалось запустить подпроцесс.');
});
1
2
3
4
5
6
import { spawn } from 'node:child_process';
const subprocess = spawn('bad_command');

subprocess.on('error', (err) => {
  console.error('Не удалось запустить подпроцесс.');
});

На некоторых платформах (macOS, Linux) заголовок процесса берётся из argv[0], на других (Windows, SunOS) — из command.

При старте Node.js перезаписывает argv[0] значением process.execPath, поэтому process.argv[0] в дочернем процессе Node.js не совпадает с параметром argv0, переданным в spawn из родителя. Используйте свойство process.argv0.

Если включена опция signal, вызов .abort() на соответствующем AbortController аналогичен .kill() у дочернего процесса, но в обработчике будет AbortError:

1
2
3
4
5
6
7
8
const { spawn } = require('node:child_process');
const controller = new AbortController();
const { signal } = controller;
const grep = spawn('grep', ['ssh'], { signal });
grep.on('error', (err) => {
  // AbortError, если контроллер вызвал abort
});
controller.abort(); // Останавливает дочерний процесс
1
2
3
4
5
6
7
8
import { spawn } from 'node:child_process';
const controller = new AbortController();
const { signal } = controller;
const grep = spawn('grep', ['ssh'], { signal });
grep.on('error', (err) => {
  // AbortError, если контроллер вызвал abort
});
controller.abort(); // Останавливает дочерний процесс

options.detached

В Windows установка options.detached в true позволяет дочернему процессу продолжать работу после выхода родителя. У дочернего процесса будет своё консольное окно. Для данного дочернего процесса это нельзя отключить.

На не-Windows платформах при options.detached: true дочерний процесс становится лидером новой группы и сессии. Дочерние процессы могут продолжать работу после выхода родителя независимо от detached. Подробнее см. setsid(2).

По умолчанию родитель ждёт завершения отсоединённого дочернего процесса. Чтобы родитель не ждал завершения subprocess, вызовите subprocess.unref(): цикл событий родителя перестанет учитывать дочерний процесс в счётчике ссылок, и родитель может завершиться независимо от потомка, если нет установленного IPC между родителем и дочерним процессом.

При запуске долгоживущего процесса с detached он не останется в фоне после выхода родителя, кроме случая, когда задана конфигурация stdio, не связанная с родителем. Если stdio родителя наследуется, дочерний процесс остаётся привязанным к управляющему терминалу.

Пример долгоживущего процесса: detached и игнорирование stdio родителя, чтобы завершение родителя не удерживало потомка:

1
2
3
4
5
6
7
8
9
const { spawn } = require('node:child_process');
const process = require('node:process');

const subprocess = spawn(process.argv[0], ['child_program.js'], {
  detached: true,
  stdio: 'ignore',
});

subprocess.unref();
1
2
3
4
5
6
7
8
9
import { spawn } from 'node:child_process';
import process from 'node:process';

const subprocess = spawn(process.argv[0], ['child_program.js'], {
  detached: true,
  stdio: 'ignore',
});

subprocess.unref();

Также можно перенаправить вывод дочернего процесса в файлы:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
const { openSync } = require('node:fs');
const { spawn } = require('node:child_process');
const out = openSync('./out.log', 'a');
const err = openSync('./out.log', 'a');

const subprocess = spawn('prg', [], {
  detached: true,
  stdio: [ 'ignore', out, err ],
});

subprocess.unref();
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import { openSync } from 'node:fs';
import { spawn } from 'node:child_process';
const out = openSync('./out.log', 'a');
const err = openSync('./out.log', 'a');

const subprocess = spawn('prg', [], {
  detached: true,
  stdio: [ 'ignore', out, err ],
});

subprocess.unref();

options.stdio

Опция options.stdio задаёт каналы между родителем и дочерним процессом. По умолчанию stdin, stdout и stderr дочернего процесса связаны с потоками subprocess.stdin, subprocess.stdout и subprocess.stderr на объекте ChildProcess. Это эквивалентно options.stdio равному ['pipe', 'pipe', 'pipe'].

Для удобства options.stdio может быть одной из строк:

  • 'pipe': эквивалент ['pipe', 'pipe', 'pipe'] (по умолчанию)
  • 'overlapped': эквивалент ['overlapped', 'overlapped', 'overlapped']
  • 'ignore': эквивалент ['ignore', 'ignore', 'ignore']
  • 'inherit': эквивалент ['inherit', 'inherit', 'inherit'] или [0, 1, 2]

Иначе options.stdio — массив: индекс соответствует fd в дочернем процессе. Fd 0, 1 и 2 — stdin, stdout и stderr; дополнительные fd создают ещё каналы. Значение в ячейке — одно из следующих:

  1. 'pipe': канал между дочерним и родительским процессом. Со стороны родителя доступен как subprocess.stdio[fd]. Для fd 0, 1 и 2 также доступны subprocess.stdin, subprocess.stdout и subprocess.stderr. Это не настоящие каналы Unix, дочерний процесс не может обращаться к ним через дескрипторы вроде /dev/fd/2 или /dev/stdout.
  2. 'overlapped': как 'pipe', но на дескрипторе установлен флаг FILE_FLAG_OVERLAPPED (нужно для асинхронного ввода-вывода у stdio в Windows). Подробнее — в документации Microsoft. На не-Windows совпадает с 'pipe'.
  3. 'ipc': канал IPC для сообщений и передачи дескрипторов файлов. У ChildProcess не больше одного stdio с 'ipc'. Включает subprocess.send(). Если потомок — Node.js, доступны process.send() и process.disconnect(), события 'disconnect' и 'message' в дочернем процессе.

    Обращение к fd IPC иначе как через process.send() или IPC с не-Node.js процессом не поддерживается.

  4. 'ignore': Node.js игнорирует этот fd у потомка. Fd 0–2 всё равно открываются; при 'ignore' для fd подставляется /dev/null.

  5. 'inherit': проброс соответствующего stdio родителя. В первых трёх позициях — process.stdin, process.stdout, process.stderr; в остальных — как 'ignore'.
  6. Объект Stream: общий readable или writable поток (tty, файл, сокет, pipe). Нижележащий дескриптор дублируется в дочернем процессе на соответствующий индекс в stdio. У потока должен быть дескриптор (у файловых — после события 'open'). Замечание: технически можно передать stdin как writable или stdout/stderr как readable, но это не рекомендуется: неверный тип потока даёт непредсказуемые ошибки и пропуск колбэков. stdin должен быть читаемым с точки зрения ожидаемого направления данных, stdout/stderr — записываемым.
  7. Положительное целое: дескриптор файла, открытый в родителе; разделяется с потомком как у Stream. Сокеты в Windows передавать нельзя.
  8. null, undefined: значение по умолчанию. Для fd 0–2 создаётся pipe; для fd ≥ 3 по умолчанию 'ignore'.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
const { spawn } = require('node:child_process');
const process = require('node:process');

// Потомок использует stdio родителя.
spawn('prg', [], { stdio: 'inherit' });

// Только stderr общий с родителем.
spawn('prg', [], { stdio: ['pipe', 'pipe', process.stderr] });

// Дополнительный fd=4 для интерфейса в стиле startd.
spawn('prg', [], { stdio: ['pipe', null, null, null, 'pipe'] });
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import { spawn } from 'node:child_process';
import process from 'node:process';

// Потомок использует stdio родителя.
spawn('prg', [], { stdio: 'inherit' });

// Только stderr общий с родителем.
spawn('prg', [], { stdio: ['pipe', 'pipe', process.stderr] });

// Дополнительный fd=4 для интерфейса в стиле startd.
spawn('prg', [], { stdio: ['pipe', null, null, null, 'pipe'] });

Если между процессами установлен канал IPC и потомок — экземпляр Node.js, канал IPC запускается с unref(), пока в потомке не зарегистрирован обработчик 'disconnect' или 'message'. Тогда процесс может завершиться, не удерживаясь открытым IPC. См. также child_process.exec() и child_process.fork().

Синхронное создание процессов

Методы child_process.spawnSync(), child_process.execSync() и child_process.execFileSync() выполняются синхронно и блокируют цикл событий Node.js до завершения порождённого процесса.

Такие блокирующие вызовы удобны для сценариев общего назначения и для загрузки или обработки конфигурации при старте приложения.

child_process.execFileSync(file[, args][, options])

  • file <string> Имя или путь исполняемого файла.
  • args <string[]> Список строковых аргументов.
  • options <Object>
    • cwd <string> | <URL> Текущий рабочий каталог дочернего процесса.
    • input <string> | <Buffer> | <TypedArray> | <DataView> Данные для stdin порождённого процесса. Если stdio[0] равен 'pipe', это значение перекрывает stdio[0].
    • stdio <string> | <Array> Конфигурация stdio дочернего процесса. См. stdio у child_process.spawn(). По умолчанию stderr идёт в stderr родителя, пока не задано иное stdio. По умолчанию: 'pipe'.
    • env <Object> Пары «ключ–значение» окружения. По умолчанию: process.env.
    • uid <number> Задаёт идентификатор пользователя процесса (см. setuid(2)).
    • gid <number> Задаёт идентификатор группы процесса (см. setgid(2)).
    • timeout <number> Максимальное время работы процесса в миллисекундах. По умолчанию: undefined.
    • killSignal <string> | <integer> Сигнал при принудительном завершении. По умолчанию: 'SIGTERM'.
    • maxBuffer <number> Максимальный объём данных в байтах для stdout или stderr. При превышении дочерний процесс завершается. См. раздел о maxBuffer и Unicode. По умолчанию: 1024 * 1024.
    • encoding <string> Кодировка для всех stdio. По умолчанию: 'buffer'.
    • windowsHide <boolean> Скрыть консольное окно подпроцесса в Windows. По умолчанию: false.
    • shell <boolean> | <string> Если true, запускает command в оболочке. На Unix — '/bin/sh', в Windows — process.env.ComSpec. См. требования к оболочке и оболочку Windows по умолчанию. По умолчанию: false (без оболочки).
  • Возвращает: <Buffer> | <string> stdout команды.

Метод child_process.execFileSync() в целом совпадает с child_process.execFile(), но не возвращает управление, пока дочерний процесс полностью не закроется. При таймауте и отправке killSignal метод ждёт полного завершения процесса.

Если потомок перехватывает SIGTERM и не завершается, родитель всё равно ждёт его завершения.

При таймауте или ненулевом коде выхода метод выбрасывает Error с полным результатом нижележащего child_process.spawnSync().

При включённой опции shell не передавайте несанированный ввод; метасимволы оболочки могут привести к выполнению произвольной команды.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const { execFileSync } = require('node:child_process');

try {
  const stdout = execFileSync('my-script.sh', ['my-arg'], {
    // Перехват stdout и stderr; иначе stderr потока шёл бы в stderr родителя
    stdio: 'pipe',

    // Кодировка utf8 для stdio
    encoding: 'utf8',
  });

  console.log(stdout);
} catch (err) {
  if (err.code) {
    // Не удалось породить дочерний процесс
    console.error(err.code);
  } else {
    // Процесс запущен, но завершился с ненулевым кодом;
    // в ошибке есть stdout и stderr потомка
    const { stdout, stderr } = err;

    console.error({ stdout, stderr });
  }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import { execFileSync } from 'node:child_process';

try {
  const stdout = execFileSync('my-script.sh', ['my-arg'], {
    // Перехват stdout и stderr; иначе stderr потока шёл бы в stderr родителя
    stdio: 'pipe',

    // Кодировка utf8 для stdio
    encoding: 'utf8',
  });

  console.log(stdout);
} catch (err) {
  if (err.code) {
    // Не удалось породить дочерний процесс
    console.error(err.code);
  } else {
    // Процесс запущен, но завершился с ненулевым кодом;
    // в ошибке есть stdout и stderr потомка
    const { stdout, stderr } = err;

    console.error({ stdout, stderr });
  }
}

child_process.execSync(command[, options])

  • command <string> Команда для выполнения.
  • options <Object>
    • cwd <string> | <URL> Текущий рабочий каталог дочернего процесса.
    • input <string> | <Buffer> | <TypedArray> | <DataView> Данные для stdin порождённого процесса. Если stdio[0] равен 'pipe', это значение перекрывает stdio[0].
    • stdio <string> | <Array> Конфигурация stdio дочернего процесса. См. stdio у child_process.spawn(). По умолчанию stderr идёт в stderr родителя, пока не задано иное stdio. По умолчанию: 'pipe'.
    • env <Object> Пары «ключ–значение» окружения. По умолчанию: process.env.
    • shell <string> Оболочка для выполнения команды. См. требования к оболочке и оболочку Windows по умолчанию. По умолчанию: '/bin/sh' на Unix, process.env.ComSpec в Windows.
    • uid <number> Задаёт идентификатор пользователя процесса (см. setuid(2)).
    • gid <number> Задаёт идентификатор группы процесса (см. setgid(2)).
    • timeout <number> Максимальное время работы процесса в миллисекундах. По умолчанию: undefined.
    • killSignal <string> | <integer> Сигнал при принудительном завершении. По умолчанию: 'SIGTERM'.
    • maxBuffer <number> Максимальный объём в байтах для stdout или stderr; при превышении процесс завершается, вывод обрезается. См. раздел о maxBuffer и Unicode. По умолчанию: 1024 * 1024.
    • encoding <string> Кодировка для всех stdio. По умолчанию: 'buffer'.
    • windowsHide <boolean> Скрыть консольное окно подпроцесса в Windows. По умолчанию: false.
  • Возвращает: <Buffer> | <string> stdout команды.

Метод child_process.execSync() в целом совпадает с child_process.exec(), но не возвращает управление, пока дочерний процесс полностью не закроется. При таймауте и killSignal метод ждёт полного завершения. Если потомок перехватывает SIGTERM и не выходит, родитель ждёт его завершения.

При таймауте или ненулевом коде выхода метод выбрасывает исключение. Объект Error содержит полный результат child_process.spawnSync().

Никогда не передавайте несанированный пользовательский ввод. Метасимволы оболочки могут привести к выполнению произвольной команды.

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

  • command <string> Команда для запуска.
  • args <string[]> Список строковых аргументов.
  • options <Object>
    • cwd <string> | <URL> Текущий рабочий каталог дочернего процесса.
    • input <string> | <Buffer> | <TypedArray> | <DataView> Данные для stdin порождённого процесса. Если stdio[0] равен 'pipe', это значение перекрывает stdio[0].
    • argv0 <string> Явно задаёт argv[0] для дочернего процесса; если не указано — command.
    • stdio <string> | <Array> Конфигурация stdio дочернего процесса. См. stdio у child_process.spawn(). По умолчанию: 'pipe'.
    • env <Object> Пары «ключ–значение» окружения. По умолчанию: process.env.
    • uid <number> Задаёт идентификатор пользователя процесса (см. setuid(2)).
    • gid <number> Задаёт идентификатор группы процесса (см. setgid(2)).
    • timeout <number> Максимальное время работы процесса в миллисекундах. По умолчанию: undefined.
    • killSignal <string> | <integer> Сигнал при принудительном завершении. По умолчанию: 'SIGTERM'.
    • maxBuffer <number> Максимальный объём в байтах для stdout или stderr; при превышении процесс завершается, вывод обрезается. См. раздел о maxBuffer и Unicode. По умолчанию: 1024 * 1024.
    • encoding <string> Кодировка для всех stdio. По умолчанию: 'buffer'.
    • shell <boolean> | <string> Если true, запускает command в оболочке. На Unix — '/bin/sh', в Windows — process.env.ComSpec. См. требования к оболочке и оболочку Windows по умолчанию. По умолчанию: false (без оболочки).
    • windowsVerbatimArguments <boolean> В Windows аргументы не экранируются. На Unix игнорируется. Автоматически true, если задан shell и это CMD. По умолчанию: false.
    • windowsHide <boolean> Скрыть консольное окно подпроцесса в Windows. По умолчанию: false.
  • Возвращает: <Object>
    • pid <number> PID дочернего процесса.
    • output <Array> Результаты вывода stdio.
    • stdout <Buffer> | <string> Содержимое output[1].
    • stderr <Buffer> | <string> Содержимое output[2].
    • status <number> | null Код выхода подпроцесса или null, если завершение по сигналу.
    • signal <string> | null Сигнал завершения или null, если не по сигналу.
    • error <Error> Объект ошибки при сбое или таймауте.

Метод child_process.spawnSync() в целом совпадает с child_process.spawn(), но не возвращает управление, пока дочерний процесс полностью не закроется. При таймауте и killSignal метод ждёт полного завершения. Если процесс перехватывает SIGTERM и не выходит, родитель ждёт его завершения.

При включённой опции shell не передавайте несанированный ввод; метасимволы оболочки могут привести к выполнению произвольной команды.

Класс: ChildProcess

Экземпляры ChildProcess представляют порождённые дочерние процессы.

Экземпляры ChildProcess не предназначены для прямого создания. Используйте child_process.spawn(), child_process.exec(), child_process.execFile() или child_process.fork().

Событие: 'close'

  • code <number> Код выхода, если процесс завершился сам, или null, если по сигналу.
  • signal <string> Сигнал завершения или null, если завершение не по сигналу.

Событие 'close' генерируется после завершения процесса и закрытия всех stdio потоков дочернего процесса. Оно отличается от 'exit', так как несколько процессов могут разделять одни и те же stdio. 'close' всегда следует после 'exit', либо после 'error', если породить процесс не удалось.

Если процесс завершился штатно, code — итоговый код выхода, иначе null. Если завершение по сигналу, signal — имя сигнала, иначе null. Ровно одно из двух полей всегда не null.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
const { spawn } = require('node:child_process');
const ls = spawn('ls', ['-lh', '/usr']);

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

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

ls.on('exit', (code) => {
  console.log(`child process exited with code ${code}`);
});
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
import { spawn } from 'node:child_process';
import { once } from 'node:events';
const ls = spawn('ls', ['-lh', '/usr']);

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

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

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

const [code] = await once(ls, 'close');
console.log(`child process close all stdio with code ${code}`);

Событие: 'disconnect'

Событие 'disconnect' генерируется после вызова subprocess.disconnect() в родителе или process.disconnect() в дочернем процессе. После отключения отправка и приём сообщений невозможны, свойство subprocess.connected равно false.

Событие: 'error'

Событие 'error' генерируется, если:

  • процесс не удалось породить;
  • процесс не удалось завершить сигналом;
  • не удалось отправить сообщение дочернему процессу;
  • дочерний процесс прерван через опцию signal.

Событие 'exit' после ошибки может возникнуть, а может и нет. При подписке и на 'exit', и на 'error' избегайте повторных вызовов обработчиков.

См. также subprocess.kill() и subprocess.send().

Событие: 'exit'

  • code <number> Код выхода, если процесс завершился сам, или null, если по сигналу.
  • signal <string> Сигнал завершения или null, если завершение не по сигналу.

Событие 'exit' генерируется после завершения дочернего процесса. Если процесс завершился штатно, code — итоговый код выхода, иначе null. Если завершение по сигналу, signal — имя сигнала, иначе null. Ровно одно из двух полей всегда не null.

При событии 'exit' потоки stdio дочернего процесса могут быть ещё открыты.

Node.js устанавливает обработчики SIGINT и SIGTERM и не завершается сразу при их получении: выполняется очистка и затем сигнал пробрасывается снова.

См. waitpid(2).

Если code равен null из‑за сигнала, для перевода в POSIX-код выхода используйте util.convertProcessSignalToExitCode().

Событие: 'message'

Событие 'message' возникает, когда дочерний процесс вызывает process.send().

Сообщение сериализуется и разбирается; итог может отличаться от исходного.

Если при порождении был задан serialization: 'advanced', аргумент message может содержать данные, которые JSON не представляет. Подробнее — расширенная сериализация.

Событие: 'spawn'

Событие 'spawn' генерируется один раз после успешного порождения дочернего процесса. Если породить процесс не удалось, 'spawn' не приходит, вместо него — 'error'.

Если событие есть, оно предшествует остальным и любым данным из stdout или stderr.

'spawn' приходит, даже если ошибка возникает внутри уже запущенного процесса. Например, bash some-command успешно стартует — будет 'spawn', хотя bash может не запустить some-command. То же при { shell: true }.

subprocess.channel

  • Тип: <Object> Канал (pipe), представляющий IPC к дочернему процессу.

Свойство subprocess.channel ссылается на IPC-канал дочернего процесса. Если IPC нет, значение undefined.

subprocess.channel.ref()

Делает так, чтобы IPC-канал удерживал цикл событий родителя, если ранее вызывался .unref().

subprocess.channel.unref()

IPC-канал перестаёт удерживать цикл событий родителя; цикл может завершиться, пока канал ещё открыт.

subprocess.connected

  • Тип: <boolean> Становится false после вызова subprocess.disconnect().

Свойство subprocess.connected показывает, можно ли ещё обмениваться сообщениями с дочерним процессом. При false отправка и приём невозможны.

subprocess.disconnect()

Закрывает IPC между родителем и потомком, позволяя дочернему процессу корректно завершиться, когда больше нет других удерживающих соединений. После вызова subprocess.connected и process.connected в родителе и потомке (соответственно) становятся false, сообщения передавать нельзя.

Событие 'disconnect' приходит, когда не осталось сообщений в процессе приёма; чаще всего сразу после subprocess.disconnect().

Если потомок — экземпляр Node.js (например, через child_process.fork()), в нём можно вызвать process.disconnect() для закрытия IPC.

subprocess.exitCode

Свойство subprocess.exitCode — код выхода дочернего процесса. Пока процесс ещё работает, значение null.

При завершении по сигналу subprocess.exitCode равен null, а subprocess.signalCode заполнен. Для соответствующего POSIX-кода выхода используйте util.convertProcessSignalToExitCode(subprocess.signalCode).

subprocess.kill([signal])

Метод subprocess.kill() отправляет сигнал дочернему процессу. Без аргумента отправляется 'SIGTERM'. Список сигналов см. в signal(7). Возвращает true, если kill(2) успешен, иначе false.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
const { spawn } = require('node:child_process');
const grep = spawn('grep', ['ssh']);

grep.on('close', (code, signal) => {
  console.log(
    `child process terminated due to receipt of signal ${signal}`);
});

// Отправить процессу SIGHUP
grep.kill('SIGHUP');
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import { spawn } from 'node:child_process';
const grep = spawn('grep', ['ssh']);

grep.on('close', (code, signal) => {
  console.log(
    `child process terminated due to receipt of signal ${signal}`);
});

// Отправить процессу SIGHUP
grep.kill('SIGHUP');

Объект ChildProcess может сгенерировать 'error', если сигнал не доставлен. Отправка сигнала уже завершившемуся процессу не считается ошибкой, но может иметь непредвиденные последствия: если PID уже переназначен другому процессу, сигнал получит он.

Несмотря на имя kill, переданный сигнал может не завершить процесс.

См. kill(2).

В Windows, где нет POSIX-сигналов, аргумент signal игнорируется, кроме 'SIGKILL', 'SIGTERM', 'SIGINT' и 'SIGQUIT'; процесс всегда завершается принудительно (как при 'SIGKILL'). Подробнее — события сигналов.

В Linux при завершении родителя не завершаются «внуки» — это часто при запуске через оболочку или с опцией shell у ChildProcess:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
const { spawn } = require('node:child_process');

const subprocess = spawn(
  'sh',
  [
    '-c',
    `node -e "setInterval(() => {
      console.log(process.pid, 'is alive')
    }, 500);"`,
  ], {
    stdio: ['inherit', 'inherit', 'inherit'],
  },
);

setTimeout(() => {
  subprocess.kill(); // Не завершает процесс Node.js внутри оболочки
}, 2000);
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import { spawn } from 'node:child_process';

const subprocess = spawn(
  'sh',
  [
    '-c',
    `node -e "setInterval(() => {
      console.log(process.pid, 'is alive')
    }, 500);"`,
  ], {
    stdio: ['inherit', 'inherit', 'inherit'],
  },
);

setTimeout(() => {
  subprocess.kill(); // Не завершает процесс Node.js внутри оболочки
}, 2000);

subprocess[Symbol.dispose]()

Вызывает subprocess.kill() с 'SIGTERM'.

subprocess.killed

  • Тип: <boolean> Становится true, после того как subprocess.kill() успешно отправил сигнал дочернему процессу.

Свойство subprocess.killed показывает, получил ли дочерний процесс сигнал от subprocess.kill(). Оно не означает, что процесс уже завершён.

subprocess.pid

Возвращает PID дочернего процесса. Если породить процесс не удалось, значение undefined и генерируется error.

1
2
3
4
5
const { spawn } = require('node:child_process');
const grep = spawn('grep', ['ssh']);

console.log(`Spawned child pid: ${grep.pid}`);
grep.stdin.end();
1
2
3
4
5
import { spawn } from 'node:child_process';
const grep = spawn('grep', ['ssh']);

console.log(`Spawned child pid: ${grep.pid}`);
grep.stdin.end();

subprocess.ref()

Вызов subprocess.ref() после subprocess.unref() восстанавливает учёт дочернего процесса в счётчике ссылок: родитель снова ждёт завершения потомка перед своим выходом.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
const { spawn } = require('node:child_process');
const process = require('node:process');

const subprocess = spawn(process.argv[0], ['child_program.js'], {
  detached: true,
  stdio: 'ignore',
});

subprocess.unref();
subprocess.ref();
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import { spawn } from 'node:child_process';
import process from 'node:process';

const subprocess = spawn(process.argv[0], ['child_program.js'], {
  detached: true,
  stdio: 'ignore',
});

subprocess.unref();
subprocess.ref();

subprocess.send(message[, sendHandle[, options]][, callback])

  • message <Object>
  • sendHandle <Handle | undefined> undefined, or a net.Socket, net.Server, or dgram.Socket object.
  • options <Object> Если задан, дополняет отправку некоторых типов дескрипторов. Поддерживаемые свойства:
    • keepOpen <boolean> При передаче net.Socket: если true, сокет остаётся открытым в отправляющем процессе. По умолчанию: false.
  • callback <Function>
  • Возвращает: <boolean>

При установленном IPC между родителем и потомком (например, через child_process.fork()) метод subprocess.send() отправляет сообщения дочернему процессу. Если потомок — Node.js, сообщения принимаются событием 'message'.

Сообщение сериализуется и разбирается; результат может отличаться от исходного.

Например, в родительском скрипте:

1
2
3
4
5
6
7
8
9
const { fork } = require('node:child_process');
const forkedProcess = fork(`${__dirname}/sub.js`);

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

// В потомке выведется: CHILD got message: { hello: 'world' }
forkedProcess.send({ hello: 'world' });
1
2
3
4
5
6
7
8
9
import { fork } from 'node:child_process';
const forkedProcess = fork(`${import.meta.dirname}/sub.js`);

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

// В потомке выведется: CHILD got message: { hello: 'world' }
forkedProcess.send({ hello: 'world' });

Дочерний файл 'sub.js' может выглядеть так:

1
2
3
4
5
6
process.on('message', (message) => {
    console.log('CHILD got message:', message);
});

// В родителе выведется: PARENT got message: { foo: 'bar', baz: null }
process.send({ foo: 'bar', baz: NaN });

У дочернего процесса Node.js есть свой process.send() для отправки сообщений родителю.

Особый случай — сообщения { cmd: 'NODE_foo' }. Префикс NODE_ в cmd зарезервирован в ядре Node.js и не попадает в событие 'message' потомка: такие сообщения идут через 'internalMessage' и обрабатываются внутри Node.js. Приложениям не следует на это опираться — поведение может измениться без предупреждения.

Необязательный аргумент sendHandle в subprocess.send() передаёт сервер или сокет TCP дочернему процессу. Потомок получит его вторым аргументом обработчика 'message'. Данные, уже принятые и буферизованные в сокете, до потомка не переданы. Передача IPC-сокетов в Windows не поддерживается.

Необязательный callback вызывается после отправки сообщения, но до того, как потомок его мог получить; аргумент — null при успехе или Error при ошибке.

Если callback не задан и отправить сообщение нельзя, ChildProcess генерирует 'error' (например, когда потомок уже завершился).

subprocess.send() возвращает false, если канал закрыт или очередь неотправленных сообщений слишком велика; иначе true. callback можно использовать для контроля потока.

Пример: передача объекта сервера

Аргумент sendHandle позволяет передать дескриптор TCP-сервера потомку, как в примере:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
const { fork } = require('node:child_process');
const { createServer } = require('node:net');

const subprocess = fork('subprocess.js');

// Поднять сервер и передать дескриптор
const server = createServer();
server.on('connection', (socket) => {
  socket.end('handled by parent');
});
server.listen(1337, () => {
  subprocess.send('server', server);
});
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import { fork } from 'node:child_process';
import { createServer } from 'node:net';

const subprocess = fork('subprocess.js');

// Поднять сервер и передать дескриптор
const server = createServer();
server.on('connection', (socket) => {
  socket.end('handled by parent');
});
server.listen(1337, () => {
  subprocess.send('server', server);
});

Дочерний процесс получает сервер так:

1
2
3
4
5
6
7
process.on('message', (m, server) => {
    if (m === 'server') {
        server.on('connection', (socket) => {
            socket.end('handled by child');
        });
    }
});

Когда сервер разделён между родителем и потомком, часть соединений может обрабатывать родитель, часть — дочерний процесс.

В примере выше сервер из node:net; для node:dgram шаги те же, но вместо 'connection' слушают 'message', а вместо server.listen() вызывают server.bind(). Это поддерживается только на Unix.

Пример: передача сокета

Аналогично можно передать дескриптор сокета через sendHandle. Ниже два потомка обрабатывают соединения с «обычным» или «особым» приоритетом:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
const { fork } = require('node:child_process');
const { createServer } = require('node:net');

const normal = fork('subprocess.js', ['normal']);
const special = fork('subprocess.js', ['special']);

// Сервер и передача сокетов потомкам; pauseOnConnect — не читать сокет до передачи
const server = createServer({ pauseOnConnect: true });
server.on('connection', (socket) => {

  // Особый приоритет...
  if (socket.remoteAddress === '74.125.127.100') {
    special.send('socket', socket);
    return;
  }
  // Обычный приоритет
  normal.send('socket', socket);
});
server.listen(1337);
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
import { fork } from 'node:child_process';
import { createServer } from 'node:net';

const normal = fork('subprocess.js', ['normal']);
const special = fork('subprocess.js', ['special']);

// Сервер и передача сокетов потомкам; pauseOnConnect — не читать сокет до передачи
const server = createServer({ pauseOnConnect: true });
server.on('connection', (socket) => {

  // Особый приоритет...
  if (socket.remoteAddress === '74.125.127.100') {
    special.send('socket', socket);
    return;
  }
  // Обычный приоритет
  normal.send('socket', socket);
});
server.listen(1337);

В subprocess.js дескриптор сокета приходит вторым аргументом обработчика:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
process.on('message', (m, socket) => {
    if (m === 'socket') {
        if (socket) {
            // Убедиться, что клиентский сокет существует: между отправкой и приёмом
            // в потомке сокет мог закрыться
            socket.end(
                `Request handled with ${process.argv[2]} priority`
            );
        }
    }
});

Не используйте .maxConnections у сокета, переданного подпроцессу: родитель не отслеживает момент уничтожения сокета.

Обработчики 'message' в потомке должны проверять наличие socket: соединение могло закрыться за время передачи.

subprocess.signalCode

Свойство subprocess.signalCode — сигнал, полученный дочерним процессом, либо null.

При завершении по сигналу subprocess.exitCode будет null. Для POSIX-кода выхода используйте util.convertProcessSignalToExitCode(subprocess.signalCode).

subprocess.spawnargs

Свойство subprocess.spawnargs — полный список аргументов командной строки, с которыми запущен дочерний процесс.

subprocess.spawnfile

Свойство subprocess.spawnfile — имя исполняемого файла дочернего процесса.

Для child_process.fork() совпадает с process.execPath. Для child_process.spawn() — имя исполняемого файла. Для child_process.exec() — имя оболочки, в которой запущен потомок.

subprocess.stderr

Поток Readable для stderr дочернего процесса.

Если при порождении stdio[2] не равен 'pipe', значение null.

subprocess.stderr — псевдоним subprocess.stdio[2]; оба свойства указывают на одно и то же.

Свойство может быть null или undefined, если процесс не удалось породить.

subprocess.stdin

Поток Writable для stdin дочернего процесса.

Если потомок ждёт весь ввод, он не продолжит работу, пока поток не закрыт через end().

Если при порождении stdio[0] не равен 'pipe', значение null.

subprocess.stdin — псевдоним subprocess.stdio[0].

Свойство может быть null или undefined, если процесс не удалось породить.

subprocess.stdio

Разреженный массив каналов к дочернему процессу для позиций опции stdio в child_process.spawn(), где задано 'pipe'. subprocess.stdio[0], [1] и [2] доступны также как subprocess.stdin, subprocess.stdout и subprocess.stderr.

В примере ниже только fd 1 (stdout) потомка — 'pipe', поэтому только subprocess.stdio[1] у родителя — поток, остальные элементы null.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
const assert = require('node:assert');
const fs = require('node:fs');
const child_process = require('node:child_process');

const subprocess = child_process.spawn('ls', {
  stdio: [
    0, // stdin родителя для потомка
    'pipe', // stdout потомка в родителя
    fs.openSync('err.out', 'w'), // stderr потомка в файл
  ],
});

assert.strictEqual(subprocess.stdio[0], null);
assert.strictEqual(subprocess.stdio[0], subprocess.stdin);

assert(subprocess.stdout);
assert.strictEqual(subprocess.stdio[1], subprocess.stdout);

assert.strictEqual(subprocess.stdio[2], null);
assert.strictEqual(subprocess.stdio[2], subprocess.stderr);
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
import assert from 'node:assert';
import fs from 'node:fs';
import child_process from 'node:child_process';

const subprocess = child_process.spawn('ls', {
  stdio: [
    0, // stdin родителя для потомка
    'pipe', // stdout потомка в родителя
    fs.openSync('err.out', 'w'), // stderr потомка в файл
  ],
});

assert.strictEqual(subprocess.stdio[0], null);
assert.strictEqual(subprocess.stdio[0], subprocess.stdin);

assert(subprocess.stdout);
assert.strictEqual(subprocess.stdio[1], subprocess.stdout);

assert.strictEqual(subprocess.stdio[2], null);
assert.strictEqual(subprocess.stdio[2], subprocess.stderr);

subprocess.stdio может быть undefined, если процесс не удалось породить.

subprocess.stdout

Поток Readable для stdout дочернего процесса.

Если при порождении stdio[1] не равен 'pipe', значение null.

subprocess.stdout — псевдоним subprocess.stdio[1].

1
2
3
4
5
6
7
const { spawn } = require('node:child_process');

const subprocess = spawn('ls');

subprocess.stdout.on('data', (data) => {
  console.log(`Received chunk ${data}`);
});
1
2
3
4
5
6
7
import { spawn } from 'node:child_process';

const subprocess = spawn('ls');

subprocess.stdout.on('data', (data) => {
  console.log(`Received chunk ${data}`);
});

Свойство может быть null или undefined, если процесс не удалось породить.

subprocess.unref()

По умолчанию родитель ждёт завершения отсоединённого дочернего процесса. Чтобы не ждать subprocess, вызовите subprocess.unref(): цикл событий родителя перестанет учитывать потомка в счётчике ссылок, и родитель может завершиться независимо, если нет IPC между родителем и потомком.

1
2
3
4
5
6
7
8
9
const { spawn } = require('node:child_process');
const process = require('node:process');

const subprocess = spawn(process.argv[0], ['child_program.js'], {
  detached: true,
  stdio: 'ignore',
});

subprocess.unref();
1
2
3
4
5
6
7
8
9
import { spawn } from 'node:child_process';
import process from 'node:process';

const subprocess = spawn(process.argv[0], ['child_program.js'], {
  detached: true,
  stdio: 'ignore',
});

subprocess.unref();

maxBuffer и Unicode

Опция maxBuffer задаёт максимальное число байт в stdout или stderr. При превышении дочерний процесс завершается. Это важно для многобайтовых кодировок (UTF-8, UTF-16). Например, console.log('中文测试') отправляет в stdout 13 байт в UTF-8 при четырёх символах.

Требования к оболочке

Оболочка должна понимать ключ -c. Для 'cmd.exe' нужны ключи /d /s /c и совместимый разбор командной строки.

Оболочка Windows по умолчанию

Microsoft требует, чтобы %COMSPEC% указывал на 'cmd.exe', но дочерние процессы не всегда подчиняются тому же правилу. В функциях child_process, где можно породить оболочку, при отсутствии process.env.ComSpec используется запасной вариант 'cmd.exe'.

Расширенная сериализация

Для IPC дочерние процессы поддерживают сериализацию на основе API сериализации модуля node:v8 и алгоритма структурированного клонирования HTML. Это мощнее JSON и покрывает больше встроенных типов: BigInt, Map, Set, ArrayBuffer, TypedArray, Buffer, Error, RegExp и т.д.

Формат не является полным надмножеством JSON: например, собственные свойства на объектах встроенных типов через сериализацию не проходят. Производительность может отличаться от JSON в зависимости от данных. Включение — явно, через опцию serialization: 'advanced' при вызове child_process.spawn() или child_process.fork().

Комментарии