跳转到内容

Ruby 编程/运行多个进程

来自 Wikibooks,开放世界中的开放书籍

运行多个进程

[编辑 | 编辑源代码]

在 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

[编辑 | 编辑源代码]

对于这些命令中的许多,在子进程仍在运行时没有办法获取其 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

Windows:如何在不打开命令窗口的情况下运行 ruby

[编辑 | 编辑源代码]

通常,如果您使用 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]

[编辑 | 编辑源代码]

这里 有一个很好的背景信息(找到“运行多个进程”部分)。

这里 有些很好的参考链接。

华夏公益教科书