2011-08-19 20 views
5

鑑於我希望從長命令中測試非阻塞讀取,我創建了以下腳本,將其保存爲long,使其可執行文件爲chmod 755,並將其放置在我的路徑中(保存爲~/bin/long,其中~/bin位於我的路徑中)。爲什麼IO :: WaitReadable對STDOUT的提升與STDERR不同?

我處於* nix變體,ruby 1.9.2p290 (2011-07-09 revision 32553) [x86_64-darwin11.0.0]編譯爲RVM默認值。我不使用Windows,因此如果你這樣做,我不確定測試腳本是否適用於你。

#!/usr/bin/env ruby 

3.times do 
    STDOUT.puts 'message on stdout' 
    STDERR.puts 'message on stderr' 
    sleep 1 
end 

爲什麼long_err產生每個STDERR消息,因爲它是由

def long_err(bash_cmd = 'long', maxlen = 4096) 
    stdin, stdout, stderr = Open3.popen3(bash_cmd) 
    begin 
    begin 
     puts 'err -> ' + stderr.read_nonblock(maxlen) 
    end while true 
    rescue IO::WaitReadable 
    IO.select([stderr]) 
    retry 
    rescue EOFError 
    puts 'EOF' 
    end 
end 

long_out保持阻塞,直到所有STDOUT信息,則打印「長」印?

def long_out(bash_cmd = 'long', maxlen = 4096) 
    stdin, stdout, stderr = Open3.popen3(bash_cmd) 
    begin 
    begin 
     puts 'out -> ' + stdout.read_nonblock(maxlen) 
    end while true 
    rescue IO::WaitReadable 
    IO.select([stdout]) 
    retry 
    rescue EOFError 
    puts 'EOF' 
    end 
end 

我假設你在測試任何函數之前都會require 'open3'

爲什麼IO::WaitReadable對STDOUT的提升與STDERR不同?

使用other ways to start subprocesses的變通辦法也讚賞,如果你有他們。

回答

4

在大多數操作系統的標準輸出是緩衝而STDERR不是。 popen3的功能基本上是在你啓動和Ruby之間打開一個管道。

任何輸出,在緩衝模式下不通過該管道發送直到:

  1. 的緩衝區被填滿(由此迫使沖洗)。
  2. 發送應用程序退出(到達EOF,強制刷新)。
  3. 流明確刷新。

原因STDERR沒有緩衝的是,它通常被認爲是重要的錯誤信息立即出現,而不是通過緩衝去的效率。

所以,知道了這些,你可以模擬STDERR行爲與STDOUT這樣的:

#!/usr/bin/env ruby 

3.times do 
    STDOUT.puts 'message on stdout' 
    STDOUT.flush 
    STDERR.puts 'message on stderr' 
    sleep 1 
end 

,你會看到其中的差別。

您可能還想檢查「Understanding Ruby and OS I/O buffering」。

+0

非常感謝卡斯帕,我會盡快達到15位代表 –

0

這是迄今爲止我開展子過程的最佳選擇。我發佈了很多網絡命令,因此如果他們花費很長時間回來,我需要一種方法來計時。在任何想要控制執行路徑的情況下,這應該很方便。

我適於此從Gist,添加代碼以測試命令的退出狀態3分的結果:

  1. 成功完成(退出狀態0)
  2. 異常結束(退出狀態是非零) - 引發一個例外
  3. 命令超時並且被殺害 - 拋出一個異常

而且固定的競爭條件,簡化參數,更增添了一些COM並添加了調試代碼,以幫助我瞭解退出和信號發生了什麼。

這樣調用該函數:

output = run_with_timeout("command that might time out", 15) 

輸出將包含命令的組合STDOUT和STDERR,如果它成功完成。如果命令在15秒內沒有完成,它將被殺死並引發異常。

這裏的(2個常量,你會在頂部需要定義)功能:

DEBUG = false  # change to true for some debugging info 
BUFFER_SIZE = 4096 # in bytes, this should be fine for many applications 

def run_with_timeout(command, timeout) 
    output = '' 
    tick = 1 
    begin 
    # Start task in another thread, which spawns a process 
    stdin, stderrout, thread = Open3.popen2e(command) 
    # Get the pid of the spawned process 
    pid = thread[:pid] 
    start = Time.now 

    while (Time.now - start) < timeout and thread.alive? 
     # Wait up to `tick' seconds for output/error data 
     Kernel.select([stderrout], nil, nil, tick) 
     # Try to read the data 
     begin 
     output << stderrout.read_nonblock(BUFFER_SIZE) 
     puts "we read some data..." if DEBUG 
     rescue IO::WaitReadable 
     # No data was ready to be read during the `tick' which is fine 
     print "."  # give feedback each tick that we're waiting 
     rescue EOFError 
     # Command has completed, not really an error... 
     puts "got EOF." if DEBUG 
     # Wait briefly for the thread to exit... 
     # We don't want to kill the process if it's about to exit on its 
     # own. We decide success or failure based on whether the process 
     # completes successfully. 
     sleep 1 
     break 
     end 
    end 

    if thread.alive? 
     # The timeout has been reached and the process is still running so 
     # we need to kill the process, because killing the thread leaves 
     # the process alive but detached. 
     Process.kill("TERM", pid) 
    end 

    ensure 
    stdin.close if stdin 
    stderrout.close if stderrout 
    end 

    status = thread.value   # returns Process::Status when process ends 

    if DEBUG 
    puts "thread.alive?: #{thread.alive?}" 
    puts "status: #{status}" 
    puts "status.class: #{status.class}" 
    puts "status.exited?: #{status.exited?}" 
    puts "status.exitstatus: #{status.exitstatus}" 
    puts "status.signaled?: #{status.signaled?}" 
    puts "status.termsig: #{status.termsig}" 
    puts "status.stopsig: #{status.stopsig}" 
    puts "status.stopped?: #{status.stopped?}" 
    puts "status.success?: #{status.success?}" 
    end 

    # See how process ended: .success? => true, false or nil if exited? !true 
    if status.success? == true  # process exited (0) 
    return output 
    elsif status.success? == false # process exited (non-zero) 
    raise "command `#{command}' returned non-zero exit status (#{status.exitstatus}), see below output\n#{output}" 
    elsif status.signaled?   # we killed the process (timeout reached) 
    raise "shell command `#{command}' timed out and was killed (timeout = #{timeout}s): #{status}" 
    else 
    raise "process didn't exit and wasn't signaled. We shouldn't get to here." 
    end 
end 

希望這是非常有用的。

相關問題