在 Ruby 中執行 Shell 命令的 6 種方法

weakish發表於2019-02-16

我們時常會與作業系統互動或在 Ruby 中執行 Shell 命令。Ruby為我們提供了完成該任務的諸多方法。

  1. Exec

    Kernel#exec 通過執行給定的命令來替換當前程式,例如:

    $ irb
    >> exec `echo "hello $HOSTNAME"`
    hello codefun
    $
    

    注意 exec 利用 echo 命令替換了 irb 程式,然後退出。因為 Ruby 實際上結束了該方法,所以只能有限使用。該方法的缺點是,你無法從 Ruby 指令碼中知道命令是執行成功還是失敗。

  2. System

    system 命令與 exec 操作相似,但它是在 subshell 中執行,而不是替換當前程式。與 exec 相比,system 給我們更多的資訊。如果命令執行成功,它返回 true;否則返回 false。

    $irb
    >> system `echo "hello $HOSTNAME"` 
    hello codefun
    => true
    >> system `false`
    => false
    >> puts $?
    256
    => nil
    >>
    

    system 將程式的退出狀態設定到全域性變數 $?。注意 false 命令的退出狀態,總是非 0 值。檢查退出碼讓我們既可以丟擲(raise)異常,又能夠重試(retry)命令。

    新手注意:Unix 命令執行成功退出碼為 0,否則為非 0。

    如果我們想知道“命令是否執行成功呢?”,使用 system 就很好。然而,我們時常想要捕獲命令的輸出,並在程式中使用。

  3. Backticks (`)

    backticks(也叫“backquotes”)在 subshell 中執行命令,並從該命令返回標準輸出。

    $ irb
    >> today = `date`
    => "Wed Jul  4 22:03:15 CST 2012
    "
    >> $?
    => #<Process::Status: pid 6169 exit 0>
    >> $?.to_i
    => 0
    

    這也許是在 subshell 中執行命令最廣為人知的方法。如你所見,它返回了命令的輸出,然後我們可以像其他字串一樣使用它。

    注意 $? 並非是返回狀態的整數,而實際是 Process::Status 物件。我們不僅得到了退出狀態,而且有程式 ID。Process::Status#to_i 返回整數型的退出狀態(#to_s 返回字串型的退出狀態)。

    使用 backticks 我們只能獲得命令的標準輸出(stdout),而不能獲得其標準錯 誤(stderr)。在下面的例子中,我們執行 Perl 指令碼來輸出字串到標準錯誤。

    $ irb
    >> warning = `perl -e "warn `dust in the wind`"`
    dust in the wind at -e line 1.
    => ""
    >> puts warning
    
    => nil
    

    注意變數 warning 沒有被設定。當我們在 Perl 中執行 warn
    時,產生的標準錯誤輸出並沒有被 backticks 捕獲。

  4. IO#popen

    IO#popen 是在子程式中執行命令的另一種方法。popen 給你更多的控制,子程式的標準輸入和標準輸出都會連線到 IO 物件。

    $ irb
    >> IO.popen("date") { |f| puts f.gets }
    Wed Jul  4 22:02:31 CST 2012
    => nil
    

    雖然 IO#popen 不錯,但是當需要這類細分層次的資訊時,我通常使用Open3#popen3

  5. Open3#popen3

    Ruby 標準庫包含 Open3 類。它易用,並能返回標準輸入、標準輸出、以及標準 錯誤。在本例中,讓我們使用互動命令 dc。dc 是從標準輸入讀取的逆波計算器(reverse-polish calculator)。我們先 push 兩個數字和一個操作符到堆疊 中。然後我們使用 p 來列印輸出結果。下面我們 push 5、10 及 +,結果標準輸出獲得了15。

    $ irb
    >> require `open3`
    => true
    >> stdin, stdout, stderr = Open3.popen3(`dc`)
    => [#<IO:fd 6>, #<IO:fd 7>, #<IO:fd 9>, #<Thread:0x816d46c sleep>]
    >> stdin.puts(5)
    => nil
    >> stdin.puts(10)
    => nil
    >> stdin.puts("+")
    => nil
    >> stdin.puts("p")
    => nil
    >> stdout.gets
    => "15
    "
    

    使用該命令我們不僅可以讀取命令的輸出,而且也能寫到命令的標準輸入。這允 許我們靈活地處理與命令的互動。

    如果我們需要,popen3 也將給我們標準錯誤。

    # (irb continued...)
    >> stdin.puts("asdfasdfasdfasdf")
    => nil
    >> stderr.gets
    => "dc: stack empty
    "
    

    使用popen3 的缺點是 $? 不返回適當的退出狀態。

    $ irb
    >> require `open3`
    => true
    >> stdin, stdout, stderr = Open3.popen3(`false`)
    => [#<IO:fd 8>, #<IO:fd 10>, #<IO:fd 12>, #<Thread:0x8297644 sleep>]
    >> $?
    => nil
    >> $?.to_i
    => 0
    

    0?false 是假定返回非 0 退出狀態。該缺點帶我們到 Open4。

  6. Open4#popen4

    Open4#popen4 是 Ara Howard 建立的 Ruby Gem。它的操作與 open3 相似,例外是我們能從程式獲得退出狀態。popen4 為 subshell 返回程式 ID,這樣我們能從等候的程式獲得退出狀態。(你需要安裝 open4 gem)

    $ irb
    >> require `open4`
    => true
    >> pid, stdin, stdout, stderr = Open4::popen4 "false"
    => [17913, #<IO:fd 6>, #<IO:fd 7>, #<IO:fd 9>]
    >> $?
    => nil
    >> pid
    => 17913
    >> ignored, status = Process::waitpid2 pid
    => [17913, #<Process::Status: pid 17913 exit 1>]
    >> status.to_i
    => 256
    

    你也可以作為塊來呼叫 popen4,它將自動等候退出狀態。

    $ irb
    >> require `open4`
    => true
    >> status = Open4::popen4("false") do |pid, stdin, stdout, stderr|
    ?>   puts "PID #{pid}"
    >> end
    PID 18535
    => #<Process::Status: pid 18535 exit 1>
    >> puts status
    pid 18535 exit 1
    => nil
    

原文來自 tech.natemurray.com
翻譯 dailyrb
許可 CC-by-sa

相關文章