Как мне получить дочерний код выхода PTY.spawn? - PullRequest
5 голосов
/ 06 октября 2010

Я пытаюсь управлять SSH-соединением с сетевым устройством через модуль PTY, с кодом, подобным следующему:

cmd_line = "ssh coltrane@love.supreme.com"
begin
  PTY.spawn(cmd_line) do |r_f,w_f,pid|
  ...
rescue PTY::ChildExited => cended
  ...
end

Весь ввод / вывод работает довольно хорошо, однако я нене знает, как получить состояние завершения дочернего процесса.

Например, если соединение разорвано или просто истекло время ожидания, порожденный процесс завершится с кодом ошибки, но этот код, похоже, невозвращается в специальной переменной $?.

Ответы [ 3 ]

15 голосов
/ 01 сентября 2011

TLDR

Используйте 1.9.2 и дождитесь, пока процесс PTY правильно установит $?

PTY.spawn(command) do |r,w,pid|
  # ...
  Process.wait(pid)
end

Полная история

На 1.9.2 вы можете зафиксировать состояние выхода для PTY, вызвав функцию ожидания на пид PTY. Это работает почти все время (AFAIK). Единственные исключения, о которых я знаю, относятся к крайним случаям, таким как немедленный выход или выдача пустой строки для команды (см. http://redmine.ruby -lang.org / Issues / 5253 ).

Например:

require 'pty'
require 'test/unit'

class PTYTest < Test::Unit::TestCase
  def setup
    system "true"
    assert_equal 0, $?.exitstatus
  end

  def pty(cmd, &block)
    PTY.spawn(cmd, &block)
    $?.exitstatus
  end

  def test_pty_with_wait_correctly_sets_exit_status_for_master_slave_io
    status = pty("printf 'abc'; exit 8") do |r,w,pid|
      while !r.eof?
        r.getc
      end
      Process.wait(pid)
    end
    assert_equal 8, status
  end

  def test_pty_with_wait_correctly_sets_exit_status_for_basic_commands
    status = pty("true") do |r,w,pid|
      Process.wait(pid)
    end
    assert_equal 0, status

    status = pty("false") do |r,w,pid|
      Process.wait(pid)
    end
    assert_equal 1, status
  end

  def test_pty_with_wait_sets_exit_status_1_for_immediate_exit
    status = pty("exit 8") do |r,w,pid|
      Process.wait(pid)
    end
    assert_equal 1, status
  end

  def test_pty_with_kill
    status = pty("sleep 10") do |r,w,pid|
      Process.kill(9, pid)
      Process.wait(pid)
    end

    assert_equal nil, status
  end
end

Теперь запустите тест:

$ ruby -v
ruby 1.9.2p290 (2011-07-09 revision 32553) [x86_64-darwin10.8.0]
$ ruby example.rb
Loaded suite example
Started
....
Finished in 1.093349 seconds.

4 tests, 9 assertions, 0 failures, 0 errors, 0 skips

Test run options: --seed 31924

На 1.8.7 вам нужно сделать немного больше. В старых рубинах PTY часто выходил с ошибками PTY :: ChildExited, даже когда вы ожидаете завершения процесса PTY. В результате, если вы запустите тесты, как написано, вы получите это:

$ ruby -v
ruby 1.8.7 (2010-08-16 patchlevel 302) [i686-darwin10.4.0]
$ ruby example.rb
Loaded suite example
Started
EE.E
Finished in 1.170357 seconds.

  1) Error:
test_pty_with_kill(PTYTest):
PTY::ChildExited: pty - exited: 35196
    example.rb:11:in `test_pty_with_kill'
    example.rb:11:in `spawn'
    example.rb:11:in `pty'
    example.rb:45:in `test_pty_with_kill'

  2) Error:
test_pty_with_wait_correctly_sets_exit_status_for_basic_commands(PTYTest):
PTY::ChildExited: pty - exited: 35198
    example.rb:11:in `test_pty_with_wait_correctly_sets_exit_status_for_basic_commands'
    example.rb:11:in `spawn'
    example.rb:11:in `pty'
    example.rb:26:in `test_pty_with_wait_correctly_sets_exit_status_for_basic_commands'

  3) Error:
test_pty_with_wait_sets_exit_status_1_for_immediate_exit(PTYTest):
PTY::ChildExited: pty - exited: 35202
    example.rb:11:in `test_pty_with_wait_sets_exit_status_1_for_immediate_exit'
    example.rb:11:in `spawn'
    example.rb:11:in `pty'
    example.rb:38:in `test_pty_with_wait_sets_exit_status_1_for_immediate_exit'

4 tests, 5 assertions, 0 failures, 3 errors

Обратите внимание, ПОЧТИ все тестовые бомбы с ошибкой ChildExited, но один (кстати, тот, который представляет наиболее реалистичное использование PTY), завершается успешно, как и ожидалось. Конечно, это ошибочное поведение является ошибкой, и, как уже было показано, оно было исправлено в 1.9.2.

Однако есть частичное решение проблемы. Вы можете специально обрабатывать ошибки ChildExited, используя что-то вроде этого:

def pty(cmd, &block)
  begin
    PTY.spawn(cmd, &block)
    $?.exitstatus
  rescue PTY::ChildExited
    $!.status.exitstatus
  end
end

Вставьте это, запустите тесты снова, и вы получите результаты, соответствующие 1.9.2, с БОЛЬШИМ предупреждением, что $? не будет установлен правильно (в отличие от 1.9.2). В частности, если вы добавите этот тест:

def test_setting_of_process_status
  system "true"
  assert_equal 0, $?.exitstatus

  begin
    PTY.spawn("false") do |r,w,pid|
      Process.wait(pid)
    end
  rescue PTY::ChildExited
  end
  assert_equal 1, $?.exitstatus
end

Вы получаете успех на 1.9.2, и вы получаете провал на 1.8.7. В случае 1.8.7 PTY завершается через ошибку ChildExited - Process.wait никогда не вызывается и, следовательно, никогда не устанавливает $ ?. Вместо $? из 'system' true '' сохраняется, и вы получаете 0 вместо 1 в качестве состояния выхода.

Поведение $? за ним трудно следовать, и у меня есть больше предостережений, в которые я не войду (т.е. иногда PTY будет завершаться через Process.wait).

1 голос
/ 08 октября 2010

Хорошо, вот несколько возможных решений этой проблемы:

  • use ruby ​​ 1.9.2 PTY.check () method

  • обернуть командную строку в скрипт

К сожалению, я не могу использовать последнюю версию ruby, так как я использовал решение-обертку, чтоповторяет $?в файл в конце сценария оболочки.Код выхода читается при выходе из порожденного потомка.

Конечно, если что-то прерывает выполнение самого скрипта-обёртки, мы никогда не получим файл результатов ...

Но прихотя бы этот обходной путь можно использовать для версий Ruby

1.8.7 / 1.9.1
0 голосов
/ 06 октября 2010

Абстрактно: в Linux Родитель должен подождать (), чтобы этот ребенок узнал о состоянии выхода своего ребенка.
Код C:

int status;
wait (& status) // в части родительского кода
WEXITSTATUS (status) // макрос для возврата кода завершения возвращенного потомка

Извините. У меня нет опыта работы с ruby, чтобы предоставить вам некоторый код. С наилучшими пожеланиями :)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...