跳轉到內容

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]

[編輯 | 編輯原始碼]

這裡 有一個很好的背景資訊(找到“執行多個程序”部分)。

這裡 有些很好的參考連結。

華夏公益教科書