Команды Unix работают на сервере, но не в сессии ruby ​​ssh

Я пытаюсь научиться использовать жемчужину net-ssh для ruby. Я хочу выполнить приведенные ниже команды после входа в каталог -/home/james.

cd /
pwd
ls

Когда я делаю это со шпателем, он работает, и я могу видеть список каталогов. Но, когда я делаю это с кодом ruby, он не дает мне тот же результат.

require 'rubygems' 
require 'net/ssh'

host = 'server'
user = 'james'
pass = 'password123'

def get_ssh(host, user, pass)
    ssh = nil
    begin
        ssh = Net::SSH.start(host, user, :password => pass)
        puts "conn successful!"
    rescue
        puts "error - cannot connect to host"
    end
    return ssh
end

conn = get_ssh(host, user, pass)

def exec(linux_code, conn)
    puts linux_code
    result = conn.exec!(linux_code)
    puts result
end

exec('cd /', conn)
exec('pwd', conn)
exec('ls', conn)

conn.close

Выход -

conn successful!
cd /
nil
pwd
/home/james
ls
nil

Я ожидал, что pwd даст мне/вместо/home/james. Так оно и работает в замазке. Какая ошибка в рубиновом коде?

Ответ 1

Кажется, что каждая команда запускается в своей собственной среде, поэтому текущий каталог не переносится на exec в exec. Вы можете проверить это, если:

exec('cd / && pwd', conn)

Он напечатает /. Из документации не ясно, как заставить все команды выполняться в одной и той же среде или если это вообще возможно.

Ответ 2

Это связано с тем, что net/ssh не имеет состояния, поэтому он открывает новое соединение при выполнении каждой команды. Вы можете использовать rye gem, который реализует для этого работу. но я не знаю, работает ли он с ruby > 2, так как его разработка не так активна.

Другим способом является использование процесса pty, в котором вы откроете псевдотерминал с помощью команды ssh, чем используйте входные и выходные файлы для записи команд для терминала и чтения результатов. Чтобы прочитать результаты, вам необходимо использовать метод select класса IO. Но вам нужно научиться использовать эти утилиты, поскольку это не так очевидно для не опытного программиста.

Ответ 3

И, Эй, я нашел, как это сделать, и на самом деле это так просто. Я думаю, что в последний раз я не дошел до этого решения, потому что был немного знаком с этой штукой net-ssh, pty terminal. Но я, наконец, нашел это, и вот и пример.

require 'net/ssh'

shell = {} #this will save the open channel so that we can use it accross threads
threads = []
# the shell thread
threads << Thread.new do
  # Connect to the server
  Net::SSH.start('localhost', 'your_user_name', password: 'your_password') do |session|
    # Open an ssh channel
    session.open_channel do |channel|   
      # send a shell request, this will open an interactive shell to the server 
      channel.send_channel_request "shell" do |ch, success|    
        if success      
          # Save the channel to be used in the other thread to send commands
          shell[:ch] = ch
          # Register a data event
          # this will be triggered whenever there is data(output) from the server
          ch.on_data do |ch, data|
              puts data
          end  
        end  
      end  
    end  
  end  
end

# the commands thread
threads << Thread.new do
  loop do
    # This will prompt for a command in the terminal
    print ">"
    cmd = gets
    # Here you've to make sure that cmd ends with '\n'
    # since in this example the cmd is got from the user it ends with 
    #a trailing eol
    shell[:ch].send_data cmd
    # exit if the user enters the exit command
    break if cmd == "exit\n"
  end
end

threads.each(&:join)

и здесь мы являемся интерактивным терминалом, использующим жемчужину net-ssh ruby. Для получения дополнительной информации смотрите здесь для предыдущей версии 1, но вам так полезно понять, как работает каждая деталь. И здесь