24

I have to run some interactive shell command inside node.js. Lets our interactive shell be $ 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();

This code does not output anything (but stdout should be 2).

But if it would, there will be another problem: how to make it interactive? The process ends when I call pythonChildProcess.stdin.end();! But I just wanted to end stdin and write next stdin!

UPD: If I could emulate pressing of enter button - I would be able to interactively write to the process. But adding \n to the end of the input string does not help.

Wunsch Punsch
  • 403
  • 1
  • 3
  • 14
  • "to end stdin and write next stdin"? Can you explain what you mean with this? – E_net4 Dec 13 '14 at 13:03
  • 1
    @E_net4, I'd like to execute `$ python` command, write some input (for example `1 + 1`), "press `enter`", get some output (`2` in the case), then write another input (`10 + 10`), "press `enter`" again, get some another output, then write some another input and so on... And such sequences of "inputs" I named "stdins" – Wunsch Punsch Dec 13 '14 at 13:26
  • those are actually a single stdin stream, to which you want to send multiple lines of input. – E_net4 Dec 13 '14 at 13:32
  • `inherit`ing the parent streams is trivial. more interesting is the [actual emulation of a "headless" terminal](https://stackoverflow.com/questions/20515244), where stdout is captured to strings, and strings are passed to stdin of the child process – milahu Nov 11 '21 at 06:50

3 Answers3

27

This works great for me:

const { spawn } = require('child_process')
const shell = spawn('sh',[], { stdio: 'inherit' })
shell.on('close',(code)=>{console.log('[shell] terminated :',code)})
Adam
  • 1,779
  • 2
  • 15
  • 14
  • not sure what I'm missing; where's the interactive part? – Kyle Baker Jun 05 '20 at 20:22
  • 2
    that would be `stdio: 'inherit'`. You can read up on the different options here: https://nodejs.org/api/child_process.html#child_process_options_stdio – Robbie Cronin Aug 10 '20 at 06:52
  • 1
    But there are caveats: https://stackoverflow.com/questions/15339379/node-js-spawning-a-child-process-interactively-with-separate-stdout-and-stderr-s#comment24510829_16280786 – Robbie Cronin Aug 10 '20 at 07:11
  • 2
    I had to add `{ stdio: 'inherit', shell: true }` to get it to work for us. Seems like it works on MacOS and Windows at least. – Topher Fangio Mar 18 '22 at 02:27
11

First and foremost, one of the things preventing node from interfacing with other interactive shells is that the child application must keep its "interactive" behavior, even when stdin doesn't look like a terminal. python here knew that its stdin wasn't a terminal, so it refused to work. This can be overridden by adding the -i flag to the python command.

Second, as you well mentioned in the update, you forgot to write a new line character to the stream, so the program behaved as if the user didn't press Enter. Yes, this is the right way to go, but the lack of an interactive mode prevented you from retrieving any results.

Here's something you can do to send multiple inputs to the interactive shell, while still being able to retrieve each result one by one. This code will be resistant to lengthy outputs, accumulating them until a full line is received before performing another instruction. Multiple instructions can be performed at a time as well, which may be preferable if they don't depend on the parent process' state. Feel free to experiment with other asynchronous structures to fulfil your goal.

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's enough
      childProcess.stdin.end();
    }
  }
});


childProcess.stdin.write('1 + 0\n');
E_net4
  • 27,810
  • 13
  • 101
  • 139
  • this only works for python, how can you make it work for any arbitrary program? – Rainb Jan 07 '21 at 10:00
  • @Rainb Other than the name of the program, there also an assumption that the program's output ends with a new line when user input is expected. If that isn't the case, the program needs to be adjusted accordingly. I don't image there being a silver bullet here. Consider turning your particular problem into a new question. – E_net4 Jan 07 '21 at 10:06
6

A tl;dr version of @E_net4's answer, for those who understand just by reading the code. For a detailed explanation, please do read his answer. He has described it well.

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');

Output:

> 
1
Jayant Bhawal
  • 2,044
  • 2
  • 31
  • 32