Pavel Vorobyov
Pavel Vorobyov

Reputation: 913

Node.js readline with interactive child_process spawning

I'm working on simple CLI, one of it's function is to run ssh to remote servers. This is an example of what I mean:

#!/usr/bin/env node

var spawn = require('child_process').spawn;
var readline = require('readline');

var iface =readline.createInterface({
  input: process.stdin,
  output: process.stdout
});
iface.setPrompt("host> ");

iface.on("line", function(line) {
  if (line.trim() == "") {
    return iface.prompt();
  }
  var host = line.split()[0];
  iface.pause(); // PAUSING STDIN!
  var proc = spawn('ssh', [ host, '-l', 'root', '-o', 'ConnectTimeout=4' ], { stdio: [0,1,2] });
  proc.on("exit", function(code, signal) {
    iface.prompt();
  });
});
iface.prompt();

I use iface.pause() to make child process able to read from STDIN exclusively, if I remove this line, some symbols will be catched by remote server, another ones - by local readline. But this pause() really freezes stdin so if ssh waits for connect too long or trying to ask me for password, I can't send any character because stdin is paused. I don't really know how ssh handles (paused?) stdin after successful connect but somehow it works. The question is "How to detach stdin from readline without pausing it while interactive child process is working and to attach it back after child process is finished?"

Upvotes: 4

Views: 3213

Answers (2)

Pavel Vorobyov
Pavel Vorobyov

Reputation: 913

The problem was solved by adding setRawMode(false) right before spawning process. As far as I understand, this makes node release stdin from any transformations it makes to handle keypresses of functional keys. Solved version of code follows:

var spawn = require('child_process').spawn;
var readline = require('readline');

var iface =readline.createInterface({
  input: process.stdin,
  output: process.stdout
});
iface.setPrompt("host> ");

iface.on("line", function(line) {
  if (line.trim() == "") {
    return iface.prompt();
  }
  var host = line.split()[0];
  iface.pause(); // PAUSING STDIN!
  process.stdin.setRawMode(false); // Releasing stdin
  var proc = spawn('ssh', [ host, '-l', 'root', '-o', 'ConnectTimeout=4' ], { stdio: [0,1,2] });
  proc.on("exit", function(code, signal) {
    // Don't forget to switch pseudo terminal on again
    process.stdin.setRawMode(true); 
    iface.prompt();
  });
});

Upvotes: 7

mscdex
mscdex

Reputation: 106698

For what it's worth, one solution to this problem may be to just not use child processes for doing ssh client connections. There is a pure-JavaScript ssh client for node called ssh2. Not only would it be more lightweight than spawning a bunch of child processes, but it may be a lot easier to work with.

Upvotes: 0

Related Questions