Как запустить интерактивную команду оболочки внутри node.js?

Мне нужно запустить некоторую интерактивную команду оболочки внутри node.js. Позволяет нашей интерактивной оболочке $ python:

var cp = require('child_process');
var pythonChildProcess = cp.spawn('python');

pythonChildProcess.stdout.on("data", function(data) {
  console.log('data successfully written!', data); // never outputs anything
});

pythonChildProcess.stdin.write('1 + 1');
pythonChildProcess.stdin.end();

Этот код ничего не выводит (но stdout должен быть 2).

Но если это произойдет, возникнет другая проблема: как сделать ее интерактивной? Процесс заканчивается, когда я звоню pythonChildProcess.stdin.end();! Но я просто хотел закончить stdin и написать следующий stdin!

UPD: Если бы я мог эмулировать нажатие кнопки enter - я мог бы интерактивно писать в процесс. Но добавление \n в конец входной строки не помогает.

Ответ 1

Прежде всего, одна из вещей, препятствующих взаимодействию node с другими интерактивными оболочками, заключается в том, что дочернее приложение должно сохранять свое "интерактивное" поведение, даже если stdin не похоже на терминал. python здесь знал, что его stdin не был терминалом, поэтому он отказался работать. Это можно переопределить, добавив флаг -i в команду python.

Во-вторых, как вы уже упоминали в обновлении, вы забыли написать новый строковый символ для потока, поэтому программа вела себя так, как будто пользователь не нажал Enter. Да, это правильный путь, но отсутствие интерактивного режима не позволило вам получить какие-либо результаты.

Здесь вы можете сделать, чтобы отправить несколько входов в интерактивную оболочку, сохраняя при этом каждый результат по одному. Этот код будет устойчив к длительным выходам, накапливая их, пока не будет получена полная линия до выполнения другой команды. Одновременно может выполняться несколько инструкций, что может быть предпочтительным, если они не зависят от состояния родительского процесса. Не стесняйтесь экспериментировать с другими асинхронными структурами для достижения своей цели.

var cp = require('child_process');
var childProcess = cp.spawn('python', ['-i']);

childProcess.stdout.setEncoding('utf8')

var k = 0;
var data_line = '';

childProcess.stdout.on("data", function(data) {
  data_line += data;
  if (data_line[data_line.length-1] == '\n') {
    // we've got new data (assuming each individual output ends with '\n')
    var res = parseFloat(data_line);
    data_line = ''; // reset the line of data

    console.log('Result #', k, ': ', res);

    k++;
    // do something else now
    if (k < 5) {
      // double the previous result
      childProcess.stdin.write('2 * + ' + res + '\n');
    } else {
      // that enough
      childProcess.stdin.end();
    }
  }
});


childProcess.stdin.write('1 + 0\n');

Ответ 2

A tl; dr версия ответа @E_net4, для тех, кто понимает, просто читая код. Для подробного объяснения, пожалуйста, прочитайте его ответ. Он описал это хорошо.

var spawn = require('child_process').spawn

var p = spawn('node',['-i']);

p.stdout.on('data',function (data) {
    console.log(data.toString())
});

p.stdin.write('1 + 0\n');

Вывод:

> 
1