Рубрики
Без рубрики

Как интегрировать сценарий оболочки Python / Ruby / PHP с Node.js с использованием child_process.spawn

Автор оригинала: Hugo Di Francesco.

При запуске скрипта оболочки Python/Ruby/PHP есть случаи случаев. Этот пост смотрит на лучшие практики вокруг используя Child_Process.spawn, чтобы инкапсулировать этот вызов в Node.js/javascript.

Целью здесь состоит в том, чтобы иметь слой взаимодействия между Node.js и внешней оболочкой. Это быстрый обходной путь, если какая-то другая часть вашей системы не разработана в JavaScript.

Мы будем использовать Спон над exec Потому что мы говорим о передаче данных и потенциально больших количествах. Чтобы понять разницу между child_process.spawn и child_process.exec См. « Разница между порождением и Exec of Node.js Child_Process ».

Длинный и короткий из него используется exec Для небольших объемов данных (до 200 тыс.) Использование интерфейса буфера и Спон Для большего количества с использованием интерфейса потока.

Спон Имеет более многословный синтаксис для некоторых случаев использования, на которые мы посмотрим. Это более обслуживается для интеграции с Ruby/Python/PhP, поскольку мы можем получить больше данных, чем пару строк текста.

Полные примеры github.com/hugodf/node-run-python Отказ

Следующие примеры содержат 2 раздела:

  • Часть, которая на самом деле запускает команду оболочки, обычно вызывается функция Беги , а также
  • IIFE («Сразу вызывала выражение функции»), которая на самом деле называет это, (async () => {a ждать Run ()} ) (). Эта IIFE – хороший узор, включенный в async/await (s ee async js: история, шаблоны и gotc есть), но это просто там для иллюстрации, так как он представляет собой призыв к WRAPP Эд СП AWN звоните из другой части вашего приложения.

Позвоните в команду оболочки и войдите в систему

Использование Спон В этой ситуации наполнен, так как эхо будет возвращаться только то, что передано ему.

Пример довольно объяснительный и показывает, как использовать child_process.spawn «Оболовать» и прочитать эти данные обратно.

Спон Принимает исполняемый файл для вызова первого параметра и, необязательно, массив параметров/параметров для исполняемого файла в качестве второго параметра.

const { spawn } = require('child_process');
function run() {
  const process = spawn('echo', ['foo']);
  process.stdout.on(
    'data',
    (data) => console.log(data.toString())
  );
}
(() => {
  try {
    run()
    // process.exit(0)
  } catch (e) {
    console.error(e.stack);
    process.exit(1);
  }
})();

Позвоните Python для его версии

Мы будем двигаться довольно быстро, чтобы продемонстрировать, как мы сделаем что-то похожее на вышеизложенное с Python. Обратите внимание, как --version передается внутри массива.

Мы также создаем хороший регистратор для дифференцировки между Stdout и STDERR и связывают с ними. Так как порождение возвращает экземпляр, который имеет stdout и Стдерр Эмиттеры событий, мы можем связать наше logutput Функция на «Данные» событие, использующее .on («Данные», () => {/* Наша функция обратного вызова */ }).

Еще одним интересным тидбитом является то, что Python --version Выводит версию к Стдерр Отказ Несоответствия вокруг того, используют ли исполняемые файлы NIX NIX, используют выходные коды, STDERR и STDOUT на успех/ошибках – это крик, который нам придется иметь в виду, одновременно интегрируя Python/Ruby/другой с Node.js.

const { spawn } = require('child_process')
const logOutput = (name) => (data) => console.log(`[${name}] ${data.toString()}`)
function run() {
  const process = spawn('python', ['--version']);
process.stdout.on(
    'data',
    logOutput('stdout')
  );
process.stderr.on(
    'data',
    logOutput('stderr')
  );
}
(() => {
  try {
    run()
    process.exit(0)
  } catch (e) {
    console.error(e.stack);
    process.exit(1);
  }
})();

Выход:

$ node run.js
[stderr] Python 2.7.13

Позвоните в сценарий Python из узла

Теперь мы будем запустить полноценный сценарий Python (хотя он может также быть Ruby, PHP, Shell и т. Д.) От Node.js.

Это Script.py это просто выходит из системы Аргв («Вектор аргумента», т. Е. [«Путь/к/исполняемому»,/* Аргументы командной строки] )

import sys
print(sys.argv)

Как и в предыдущем примере, мы просто позвоним с помощью Python С пути к сценарию Python ( ./script.py ) во втором параметре.

Вот еще один Gotcha интеграции сценариев в этой моде. В этом примере путь к сценарию основан на рабочем каталоге, из которого Узел называется.

Конечно, используется обходной путь, используя путь модуль и __dirname , что, например, мог разрешить Другой-script.py Коо-находки с помощью модуля файла/узла JavaScript Calling Спон Использование: требуют («путь»). Разрешить (__ dirname, './OTHER-Script.py') Отказ

const { spawn } = require('child_process')
const logOutput = (name) => (data) => console.log(`[${name}] ${data.toString()}`)
function run() {
  const process = spawn('python', ['./script.py']);
process.stdout.on(
    'data',
    logOutput('stdout')
  );
process.stderr.on(
    'data',
    logOutput('stderr')
  );
}
(() => {
  try {
    run()
    // process.exit(0)
  } catch (e) {
    console.error(e.stack);
    process.exit(1);
  }
})();

Выход:

node run.js
\[stdout\] ['./script.py']

Передать аргументы к сценарию Python от Node.js с помощью Child_Process.spawn

Следующим этапом интеграции является возможность передавать данные из узла/JavaScript Code к сценарию Python.

Чтобы сделать это, мы просто передам больше аргументов Shell, используя аргументы Array (второй параметр для Spawn ).

const { spawn } = require('child_process')
const logOutput = (name) => (data) => console.log(`[${name}] ${data.toString()}`)
function run() {
  const process = spawn('python', ['./script.py', 'my', 'args']);
  process.stdout.on(
    'data',
    logOutput('stdout')
  );
  process.stderr.on(
    'data',
    logOutput('stderr')
  );
}
(() => {
  try {
    run()
    // process.exit(0)
  } catch (e) {
    console.error(e.stack);
    process.exit(1);
  }
})();

Наше Script.py Также просто выйдет из-за Аргв За исключением первого элемента (который является путь к скрипту).

import sys
print(sys.argv)[1:]

Вот вывод:

node run.js
\[stdout\] ['my', 'args']

Читайте child_process.spawn вывод из node.js

Приятно иметь возможность передавать данные в сценарий Python. Мы все еще не можем получить данные из сценария Python обратно в формате, который мы можем использовать в нашем приложении Node.js/JavaScript.

Решение этого – обернуть целое Спон -Каллирующая функция в обещание. Это позволяет нам решать, когда мы хотим решить или Отклонить Отказ

Чтобы отслеживать выходные потоки (ы) сценария Python, мы вручную буксируем вывод, используя массивы (один для stdout и другой для STDERR ).

Мы также добавляем слушатель на «Выход» Использование Спос (). Вкл. («Выход», (код, сигнал) => {/*, вероятно, разрешение вызова () */ }). Это где мы будем иметь в себе до Reso л VE/REJ ECT Value (ы) обещания от Python/Ruby/другой скрипт.

const { spawn } = require('child_process')
const logOutput = (name) => (data) => console.log(`[${name}] ${data}`)
function run() {
  return new Promise((resolve, reject) => {
    const process = spawn('python', ['./script.py', 'my', 'args']);
    const out = []
    process.stdout.on(
      'data',
      (data) => {
        out.push(data.toString());
        logOutput('stdout')(data);
      }
    );
    const err = []
    process.stderr.on(
      'data',
      (data) => {
        err.push(data.toString());
        logOutput('stderr')(data);
      }
    );
    process.on('exit', (code, signal) => {
      logOutput('exit')(`${code} (${signal})`)
      resolve(out);
    });
  });
}
(async () => {
  try {
    const output = await run()
    logOutput('main')(output)
    process.exit(0)
  } catch (e) {
    console.error(e.stack);
    process.exit(1);
  }
})();

Выход:

node run.js
\[stdout\] ['my', 'args']
\[main\] ['my', 'args']

Обрабатывать ошибки от child_process.spawn

Следующим UP Нам нужно обрабатывать ошибки из сценария Python/Ruby/Shell на уровне Node.js/JavaScript.

Основной способ, что исполняемые файлы * NIX, которые оно ошибается с использованием 1 Выходной код. Вот почему .on («Выход» Обработчик теперь делает проверку на фоне Код Прежде чем решить, разрешать ли или отклонить со значением (ыми).

const { spawn } = require('child_process')
const logOutput = (name) => (data) => console.log(`[${name}] ${data}`)
function run() {
  return new Promise((resolve, reject) => {
    const process = spawn('python', ['./script.py', 'my', 'args']);
const out = []
    process.stdout.on(
      'data',
      (data) => {
        out.push(data.toString());
        logOutput('stdout')(data);
      }
    );
const err = []
    process.stderr.on(
      'data',
      (data) => {
        err.push(data.toString());
        logOutput('stderr')(data);
      }
    );
process.on('exit', (code, signal) => {
      logOutput('exit')(`${code} (${signal})`)
      if (code === 0) {
        resolve(out);
      } else {
        reject(new Error(err.join('\n')))
      }
    });
  });
}
(async () => {
  try {
    const output = await run()
    logOutput('main')(output)
    process.exit(0)
  } catch (e) {
    console.error('Error during script execution ', e.stack);
    process.exit(1);
  }
})();

Выход:

node run.js
[stderr] Traceback (most recent call last):
    File "./script.py", line 3, in 
    print(sy.argv)[1:]
NameError: name 'sy' is not defined
Error during script execution Error: Traceback (most recent call last):
    File "./script.py", line 3, in 
    print(sy.argv)[1:]
NameError: name 'sy' is not defined
at ChildProcess.process.on (/app/run.js:33:16)
    at ChildProcess.emit (events.js:182:13)
    at Process.ChildProcess._handle.onexit (internal/child_process.js:240:12)

Передать структурированные данные из Python/Ruby на Node.js/JavaScript

Последний шаг к полной интеграции между скриптами RUBY/PYTHON/PHP/SHOOLL и нашим уровнем приложений Node.js/JavaScript должен иметь возможность передавать структурированные данные обратно из сценария до Node.js/javascript.

Самый простой структурированный формат данных, который имеет тенденцию быть доступен в Python/Ruby/PhP и Node.js/JavaScript, это JSON.

В сценарии Python мы печатаем json.dumps () Вывод словаря, см. Script.py :

import sys
import json
send_message_back = {
  'arguments': sys.argv[1:],
  'message': """Hello,
This is my message.
To the world"""
}
print(json.dumps(send_message_back))

В узле мы добавляем какую-нибудь логику сбора JSON (используя json.parse ) в «Выход» обработчик.

A gotcha на данный момент, если, например Json.parse () Не удается из-за плохо сформированного JSON, нам нужно распространить эту ошибку. Следовательно, попробуйте/поймать, где поймать пункт Отклонить -С потенциальная ошибка: попробуйте (json.parse (out [0]))} catch (e) {reject (e)} Отказ

const { spawn } = require('child_process')
const logOutput = (name) => (message) => console.log(`[${name}] ${message}`)
function run() {
  return new Promise((resolve, reject) => {
    const process = spawn('python', ['./script.py', 'my', 'args']);
    const out = []
    process.stdout.on(
      'data',
      (data) => {
        out.push(data.toString());
        logOutput('stdout')(data);
      }
    );
    const err = []
    process.stderr.on(
      'data',
      (data) => {
        err.push(data.toString());
        logOutput('stderr')(data);
      }
    );
   process.on('exit', (code, signal) => {
      logOutput('exit')(`${code} (${signal})`)
      if (code !== 0) {
        reject(new Error(err.join('\n')))
        return
      }
      try {
        resolve(JSON.parse(out[0]));
      } catch(e) {
        reject(e);
      }
    });
  });
}
(async () => {
  try {
    const output = await run()
    logOutput('main')(output.message)
    process.exit(0)
  } catch (e) {
    console.error('Error during script execution ', e.stack);
    process.exit(1);
  }
})();

Выход:

node run.js
[stdout] {"message": "Hello,\nThis is my message.\n\nTo the world", "arguments": ["my", "args"]}
[main] Hello,
This is my message.
To the world

Это оно! Спасибо за прочтение:)

У меня открыты пятна наставничества на https://mentorcruise.com/dentor/hugodifrancesco/ Отказ Сделайте это, если вы хотите Node.js/javaScript/карьеру наставничества или не стесняйтесь твитнуть на меня @hugo__df.

И читать больше моих статей на codewithhugo.com.