English 中文(简体)
如何获取PTY.spown儿童退出代码?
原标题:How do I get the PTY.spawn child exit code?

我正试图通过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

Full Story

在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,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中,您需要做更多的工作。在较旧的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, 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永远不会被调用,因此永远不会设置$?。取而代之的是美元?从系统中,“true”持续存在,并且您得到0而不是1作为退出状态。

$的行为?很难遵循,并且有更多我不会涉及的注意事项(即有时PTY将通过Process.wait完成)。

好的,下面是这个问题的一些可能的解决方案:

  • 使用ruby1.9.2PTY.check()方法

  • 将命令行包装在脚本中

不幸的是,我不能使用最新版本的ruby,所以我使用了包装器解决方案,这与$?到包装脚本末尾的文件。当派生的子级退出时,将读取退出代码。

当然,如果有什么东西中断了包装脚本本身的执行,那么我们永远不会得到结果文件。。。

但至少这个变通方法可以用于Ruby的1.8.7/1.9.1版本

In abstract: In Linux The parent should wait()s for this child to know the exit status of his child.
C code:

int status;
wait(&status) // in the parent code part
WEXITSTATUS(status) //macro to return the exit code of the returned child

I m sorry.I don t have experience with ruby to provide you with some code. Best wishes :)





相关问题
Signed executables under Linux

For security reasons, it is desirable to check the integrity of code before execution, avoiding tampered software by an attacker. So, my question is How to sign executable code and run only trusted ...

encoding of file shell script

How can I check the file encoding in a shell script? I need to know if a file is encoded in utf-8 or iso-8859-1. Thanks

How to write a Remote DataModule to run on a linux server?

i would like to know if there are any solution to do this. Does anyone? The big picture: I want to access data over the web, using my delphi thin clients. But i´would like to keep my server/service ...

How can I use exit codes to run shell scripts sequentially?

Since cruise control is full of bugs that have wasted my entire week, I have decided the existing shell scripts I have are simpler and thus better. Here is what I have so far svn update /var/www/...

Good, free, easy-to-use C graphics libraries? [closed]

I was wondering if there were any good free graphics libraries for C that are easy to use? It s for plotting 2d and 3d graphs and then saving to a file. It s on a Linux system and there s no gnuplot ...