DevGang
Авторизоваться

Выполнение Shell команд с Node.js 

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

С Node.js мы можем запускать shell команды и обрабатывать их ввод и вывод, используя JavaScript. Поэтому мы можем написать большинство этих сложных операций на языке JavaScript вместо языка сценариев оболочки, что потенциально облегчает обслуживание программы.

В этой статье мы изучим различные способы выполнения команд оболочки в Node.js с помощью модуля child_process.

Модуль child_proccess

Node.js выполняет свой главный цикл обработки событий в одном потоке. Однако это не означает, что вся его обработка выполняется в одном потоке. Асинхронные задачи в Node.js выполняются в других внутренних потоках. Когда они завершены, код в обратном вызове, или ошибка, возвращается в основной, единственный поток.

Эти различные потоки выполняются в одном и том же процессе Node.js. Однако иногда желательно создать другой процесс для выполнения кода. Когда создается новый процесс, операционная система определяет, какой процессор он использует и как планировать свои задачи.

Модуль child_process создает новые дочерние процессы нашего основного процесса Node.js. Мы можем выполнять команды оболочки с этими дочерними процессами.

Использование внешних процессов может улучшить производительность вашего приложения, если используется их правильно. Например, если функция приложения Node.js интенсивно использует процессор, так как Node.js является однопоточным, он заблокирует выполнение других задач во время работы.

Однако мы можем делегировать этот ресурсоемкий код дочернему процессу, скажем, очень эффективной программе на C++. Наш код Node.js затем выполнит эту программу на C++ в новом процессе, не блокируя другие его действия, и когда завершит обработку своего вывода.

Две функции exec и spawn, которые мы будем использовать для выполнения команд оболочки.

Функция exec

Функция exec() создает новую оболочку и выполняет заданную команду. Выходные данные выполнения буферизуются, что означает, что они хранятся в памяти и доступны для использования в обратном вызове.

Давайте использовать функцию exec(), чтобы перечислить все папки и файлы в нашем текущем каталоге. В новом файле с именем lsExec.js напишите следующий код:

lsExec.js
const { exec } = require("child_process");

exec("ls -la", (error, stdout, stderr) => {
    if (error) {
        console.log(`error: ${error.message}`);
        return;
    }
    if (stderr) {
        console.log(`stderr: ${stderr}`);
        return;
    }
    console.log(`stdout: ${stdout}`);
});

Во-первых, нам нужен модуль child_process в нашей программе, в частности, с помощью функция exec() (получаем через деструктуризацию ES6). Далее мы вызываем exec() с двумя параметрами:

  • Строка с командой оболочки, которую мы хотим выполнить.
  • Функция обратного вызова с тремя параметрами: error, stdout, stderr.

Shell команда ls -la, которую мы запускаем, должна построчно перечислять все файлы и папки в нашем текущем каталоге, включая скрытые файлы / папки. Функция обратного вызова регистрирует, получили ли мы какое-то предупреждение в переменную error, пытаясь выполнить команду или вывод в оболочке stdout или stderr потоках.

Примечание: объект error отличается от stderr. Объект error не является нулевым, когда модуль child_process не может выполнить команду. Это может произойти, если вы попытаетесь выполнить другой Node.js скрипт, но, файл не найден. С другой стороны, если команда успешно выполняется и записывает сообщение в стандартный поток ошибок, объект stderr не будет нулевым.

Если вы запустите этот файл, вы должны увидеть вывод, подобный следующему:

$ node lsExec.js
stdout: total 0
drwxr-xr-x@ 9 arpan arpan  0 Dec  7 00:14 .
drwxr-xr-x@ 4 arpan arpan  0 Dec  7 22:09 ..
-rw-r--r--@ 1 arpan arpan  0 Dec  7 15:10 lsExec.js

child process exited with code 0

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

Функция spawn

Функция spawn() выполняет команду в новом процессе. Эта функция использует Stream API, поэтому результат ее работы будет доступен через подписку на события.

Как и прежде, мы будем использовать эту функцию для вывода списка всех папок и файлов в нашем текущем каталоге. Давайте создадим новый Node.js файл lsSpawn.js и введем следующее:

lsSpawn.js
const { spawn } = require("child_process");

const ls = spawn("ls", ["-la"]);

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

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

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

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

Мы начнем с получения функции из модуля child_process. Затем мы создаем новый процесс, который выполняет команду ls -la. Обратите внимание, как аргументы shell команды хранятся в массиве.

Затем мы подписываемся на события. Объект stdout, запускает событие data, когда команда ls записывает в поток результаты своего выполнения. Аналогичным образом, stderr запускает событие data, когда shell команда возвращает ошибку в этот поток.

error выявляются при прослушивании их непосредственно на созданном объекте, в котором хранится ссылка на команду. Вы получите ошибку, только если child_process не сможете выполнить команду.

Событие close происходит, когда команда закончила свое выполнение.

Если мы запустим этот файл, мы должны получить вывод, как раньше, с exec():

$ node lsSpawn.js
stdout: total 0
drwxr-xr-x@ 9 arpan arpan  0 Dec  7 00:14 .
drwxr-xr-x@ 4 arpan arpan  0 Dec  7 22:09 ..
-rw-r--r--@ 1 arpan arpan  0 Dec  7 15:10 lsExec.js
-rw-r--r--@ 1 arpan arpan  0 Dec  7 15:40 lsSpawn.js

child process exited with code 0

Когда использовать exec и spawn?

Основное различие между exec() и spawn() заключается в том, как они возвращают данные. Поскольку выходные данные exec() хранятся в буфере, он требует больше памяти, чем поток spawn(), который выводит данные по мере их поступления.

Как правило, если вы не ожидаете возврата больших объемов данных, вы можете использовать для простоты exec(). Хорошие примеры сценариев использования - создание папки или получение статуса файла. Однако, если вы ожидаете большого количества вывода от вашей команды, то вам следует использовать spawn(). Хорошим примером будет использование команды для управления двоичными данными, а затем загрузка их в вашу программу Node.js.

Вывод

Node.js может запускать shell команды с помощью стандартного модуля child_process. Если мы используем функцию exec(), наша команда будет запущена, и ее вывод будет доступен нам в обратном вызове. Если мы используем spawn(), его вывод будет доступен через прослушивание событий.

Источник:

#NodeJS
Комментарии
Чтобы оставить комментарий, необходимо авторизоваться

Присоединяйся в тусовку

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

Попробовать

Освой перспективную онлайн профессию!

Получить скидку