ruby – 如何获取PTY.spawn子退出代码?

我正在尝试通过PTY模块管理到网络设备的SSH连接,代码类似于:

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

整个I / O工作得很好,但是我不知道如何获得子进程的退出状态.

例如,如果连接中断或只是超时,则生成的进程将以错误代码终止,但此代码似乎不会在$?中返回.特殊变量.

最佳答案
TLDR

使用1.9.2并等待PTY进程正确设置$?

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

全文

在1.9.2上,您可以通过在PTY pid上调用wait来捕获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,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,pid|
      Process.wait(pid)
    end
    assert_equal 0,status

    status = pty("false") do |r,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,status
  end

  def test_pty_with_kill
    status = pty("sleep 10") do |r,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你需要做更多.在较旧的rubies中,即使您等待PTY进程完成,PTY也会经常以PTY :: ChildExited错误退出.因此,如果您按照编写的方式运行测试,则得到以下结果:

$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,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一致,BIG警告说$?将无法正确设置(与1.9.2不同).特别是如果您要添加此测试:

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

  begin
    PTY.spawn("false") do |r,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完成).

相关文章

linux常用进程通信方式包括管道(pipe)、有名管道(FIFO)、...
Linux性能观测工具按类别可分为系统级别和进程级别,系统级别...
本文详细介绍了curl命令基础和高级用法,包括跳过https的证书...
本文包含作者工作中常用到的一些命令,用于诊断网络、磁盘占满...
linux的平均负载表示运行态和就绪态及不可中断状态(正在io)的...
CPU上下文频繁切换会导致系统性能下降,切换分为进程切换、线...