Интерактивный сеанс SSH с использованием Net::SSH или создание сокета STDIN

Это не дубликат Как провести интерактивный сеанс SSH или Сценарий интерактивного терминала Net::SSH. Как? или Интерактивный ответ/ответ Ruby net-ssh

Я пытаюсь написать интерактивный SSH-клиент, используя Net:SSH, потому что я уже использую его для запуска неинтерактивных команд на целевых хостах.

Я мог бы просто раскошелиться на system "ssh", но это потребовало бы преобразования настроек подключения, проксирования и т. д. в параметры ssh.

Проблема заключается в потоковой передаче данных из STDIN в канал оболочки. Документация Net::SSH для listen_to показывает, как это сделать, когда ввод осуществляется из сокета, а не из стандартного ввода. $stdin или IO.console не являются сокетами и поэтому несовместимы с Net::SSH::BufferedIo.

Есть ли способ создать сокет из STDIN, который можно использовать для этого? Или есть лучший способ отправить все от STDIN до Net::SSH::Channel, пока канал не закроется?

Вот код, который работает, но слишком медленный, чтобы его можно было использовать:

require 'net/ssh'
require 'io/console'
require 'io/wait'

Net::SSH.start('localhost', 'root') do |session|
  session.open_channel do |channel|
    channel.on_data do |_, data|
      $stdout.write(data)
    end

    channel.on_extended_data do |_, data|
      $stdout.write(data)
    end

    channel.request_pty do
      channel.send_channel_request "shell"
    end

    channel.connection.loop do
      $stdin.raw do |io|
        input = io.readpartial(1024)
        channel.send_data(input) unless input.empty?
      end
      channel.active?
    end
  end.wait
end

person Kimmo Lehto    schedule 26.10.2018    source источник


Ответы (1)


Сокеты на самом деле не более чем файловые дескрипторы, а поскольку STDIN также является файловым дескриптором, попробовать не помешает.

Однако вам нужно сначала перевести TTY в необработанный режим, чтобы получить интерактивность.
Этот код работает нормально:

begin
  system('stty cbreak -echo')

  Net::SSH.start(...) do |session|
    session.open_channel do |...|
       ...
       session.listen_to(STDIN) { |stdin|
         input = stdin.readpartial(1024)
         channel.send_data(input) unless input.empty?
       }
     end.wait
   end
ensure
  system('stty sane')
end
person Casper    schedule 26.10.2018
comment
Надо было попробовать без Net::SSH::BufferedIo, что вызвало NoMethodError вместо STDIN.recv. Похоже, это работает. Я считаю, что материал stty можно заменить на $stdin.raw!; $stdin.echo = false;. Спасибо! - person Kimmo Lehto; 26.10.2018