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]
這裡 有一個很好的背景資訊(找到“執行多個程序”部分)。
這裡 有些很好的參考連結。