Ruby 编程/运行多个进程
在 Ruby 中有几种方法可以运行外部命令。
output = `command here` # gives you back full stdout stdout_and_stdin = IO.popen("command here") require 'popen3' # require 'open3' in 1.9 input,output,error,running_thread_on_19_or_greater = Open3.popen3("command here") # or the same: Open3.popen3("command here") do |stdin, stdout, stderr| # ... end pid = fork { puts 'in child process' } # posix platforms only pid = Process.spawn "ls" # 1.9.x only pid = Process.daemon "ls" # 1.9.x only, basically does a spawn and a disassociate on that pid.
Process.spawn
(仅 1.9) 有 很多 选项。
对于这些命令中的许多,在子进程仍在运行时没有办法获取其 PID。对于 fork
,您可以通过 $?
和 fork
的返回值立即获取,对于 Process.{spawn,daemon}
,PID 是返回值。
如果您想要运行的子进程的 PID 和 I/O,您需要使用 fork
将子进程的 I/O 重定向到之前创建的管道,或者 jruby 用户可以使用可用
pid,input,output,error = IO.popen4("ls") #jruby only
方法或使用 #pid
方法(如果可用),例如
input, output, error, thread_if_on_19 = Open3.popen3 "ls" pid = thread.pid io = IO.popen("ls") pid = io.pid
在运行时,system
和 反引号
调用无法访问 PID(因为 $?
仅在每个线程的基础上可用,而它们尚未完成)。
实际上,$?
只是意味着“告诉我最近完成的子进程是什么”。
请注意,在 1.8 中,Open3.popen3
没有提供 PID。如果您想要在 1.8 Linux 中获取 PID,您可能需要创建一些管道,fork
一个新进程,并将它的管道重定向到您创建的管道。对于 Windows 用户,如果您想要在 1.8 Windows 中获取 open3 调用的 PID,您需要使用 辅助 gem 或 jruby 上的 IO.popen4。
如果您想要在 1.9 中启动一个子进程而不产生新的线程(就像 popen3 但没有额外的线程),您可以使用 Process.spawn
或创建管道,fork
一个进程,并将它的 IO 重定向到管道等等。
如果您想要在 Windows 1.8 中启动一个子进程而不产生新的线程或创建 IO 对象,您需要使用辅助 gem,例如 win32-process gem。
请注意,如果您启动一个进程并访问其 stdout/stdin/stderr 流,如果您没有以某种方式从这些流中读取,该进程可能会在填满流的缓冲区后 *阻塞*。也就是说,如果它们给出大量输出,您必须从它们中读取。
这段代码似乎有效
while !out.eof? print out.read 1024 end Process.wait out.pid }
尽管您可能不必在最后等待 PID。
您也可以直接读取完整的流输出,如下所示
print out.read
如果您想向外部进程读写,可以使用 popen3。您也可以使用 popen 通过使用“r+”(而不是“rw”)的打开流类型来实现。
另请注意,在 Windows 上,popen 默认情况下以“ascii”模式打开所有文件流。如果需要,可以将它们设置为二进制模式,请对返回给您的每个描述符调用 #binmode
(感谢 imagemagick 团队提供示例)。
例如
IO.popen("ruby", "r+") do |pipe| pipe.puts "puts 10**6" pipe.puts "__END__" pipe.gets end
此处的其他大多数示例都向子进程公开读写管道。有时您只需要其中一个。这是一种方法。
just_stdin_to_process = open("|process_name", "w")
您也可以使用相同的方法在 1.9 中为从进程传入的字符串设置编码(也可能存在其他方法)。
just_stdin_to_process = open("|process_name", "r:UTF-8")
这样打开也可能有效
stdin_and_out = IO.popen(c, "w") # outputs to stdout, stderr, but you can write to it
通常,如果您使用 rubyw.exe 而不是 ruby.exe 启动应用程序,则 ruby 应用程序的 stdout/stderr 会被管道传输到(Windows 等效的)/dev/null,因此不会出现命令窗口。
但是,如果在该应用程序中,您对 system("something_else.exe") 发出调用,那么它将弹出自己的控制台以进行输入/输出,以防它需要任何输入/输出(除非它也为您提供了 rubyw.exe 的等效项)。
请参阅 http://www.ruby-forum.com/topic/213521 以获取可能的解决方案列表。请注意,require 'win32/system' 代码段是使用 win32-system gem,您需要先安装它。目前似乎没有办法使用标准库来实现。
另一个选择是使用 ffi 直接调用 CreateProcess,从而实现几乎相同的效果:https://gist.github.com/rdp/8229520(最初来自 https://gist.github.com/jarib/280865,因此 childprocess gem 可能会为您完成其中的一些操作,或者您可以自己编写)。
rubyw.exe 的另一个选择是使用 ruby.exe 运行它,但在“最小化”的窗口中运行,以减少输出:http://www.justskins.com/forums/winapp-without-console-window-97080.html
win32-open3 gem 似乎是另一种选择:http://stackoverflow.com/questions/12684463/ruby-system-call-on-windows-without-a-popup-command-prompt
jrubyw 似乎也能够在不打开新的命令提示符窗口的情况下运行它们(jrubyw.exe)。
此外,在 Windows 中,您还可以使用“start.exe”来生成一个单独的后台进程,例如 system("start my_command.exe"),参考:http://stackoverflow.com/a/3840737/32453
假设您想要在 Ruby 中将一些进程链接在一起,相当于 bash 的 a | b | c
?
首先,您可以直接运行 a | b | c
,它会将字符串传递给 bash 并让它执行所有重定向,然后返回 stdout 的输出。您甚至可以使用链接的命令运行 popen,它也会执行相同的操作。
IO.popen("ls | grep unins").read # 与
ls | grep unins
相同
或者您可以自己编写。基本方法是打开一些管道,然后 fork
,在子进程中将 stdin/stdout 重定向到适当的管道(在本例中,每个连接将使用两个管道,因此总共 6 个),然后在每个子进程中执行所需的命令。呼!
Ruby 有一个内置的“shell”类。另请参阅 [1] 中的“方法 8”部分。[2] 显示了它在内部使用的基本语法,我相信它用于重定向。
它的要点类似于
pipe_me_in, pipe_peer_out = IO.pipe
pipe_peer_in, pipe_me_out = IO.pipe
fork do
STDIN.reopen(pipe_peer_in)
STDOUT.reopen(pipe_peer_out)
Kernel.exec("echo 33")
# this line is never executed because exec moves the process
end
pipe_peer_out.close
# file handles have to all be closed in order for the "read" method, below, to be able
# to know that it's done reading data, so it can return.
#See also http://devver.wordpress.com/2009/10/22/beware-of-pipe-duplication-in-subprocesses/
pipe_me_in.read
1.9 的 Process.spawn
具有其 :stdout
和 :stderr
简化语法,这可能使它变得更加容易,并且希望即使在 Windows 中也能实现。对于 1.8 Windows,您可能可以使用 win32/process gem,它也提供简化的 :stdout
和 :stderr
重定向。
另请参阅 1.9 的 Open3.pipeline_start
[3]
这里 有一个很好的背景信息(找到“运行多个进程”部分)。
这里 有些很好的参考链接。