鉴于我希望测试一个长命令的非阻塞读取,我创建了以下脚本,将其保存为long
,使其可以chmod 755
执行,并将其放在我的路径中(保存为~/bin/long
~/bin
在我的路径中。)
我在使用RVM默认值编译的ruby 1.9.2p290 (2011-07-09 revision 32553) [x86_64-darwin11.0.0]
的* nix变体上。我不使用Windows,因此如果你这样做,我不确定测试脚本是否适合你。
#!/usr/bin/env ruby
3.times do
STDOUT.puts 'message on stdout'
STDERR.puts 'message on stderr'
sleep 1
end
为什么long_err
生成每条STDERR消息,因为它是由“long”
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的变通方法,如果您拥有它们,也会感激不尽。
答案 0 :(得分:4)
在大多数操作系统中,STDOUT 缓冲,而STDERR则不是。 popen3
所做的基本上是在可执行的启动和Ruby之间打开一个管道。
任何处于缓冲模式的输出都不会通过此管道发送,直到:
STDERR没有被缓冲的原因是,通常认为错误消息立即出现很重要,而不是通过缓冲来提高效率。
因此,知道这一点,您可以使用STDOUT模拟STDERR行为,如下所示:
#!/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”。
答案 1 :(得分:0)
这是我到目前为止开始子进程的最好成绩。我发布了很多网络命令,所以如果它们需要很长时间才能返回,我需要一种方法来计算它们。在您希望保持对执行路径的控制的任何情况下,这都应该很方便。
我从Gist改编了这个,添加代码来测试3个结果的命令的退出状态:
还修复了竞争条件,简化参数,添加了一些注释,并添加了调试代码,以帮助我了解出口和信号发生的情况。
像这样调用函数:
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
希望这很有用。