這個套件是 os/exec.Cmd 的一個小但非常有用的包裝器,它使得在高度並發、非同步、即時應用程式中運行外部命令變得安全、簡單。它適用於 Linux、macOS 和 Windows。這是基本用法:
import (
"fmt"
"time"
"github.com/go-cmd/cmd"
)
func main () {
// Start a long-running process, capture stdout and stderr
findCmd := cmd . NewCmd ( "find" , "/" , "--name" , "needle" )
statusChan := findCmd . Start () // non-blocking
ticker := time . NewTicker ( 2 * time . Second )
// Print last line of stdout every 2s
go func () {
for range ticker . C {
status := findCmd . Status ()
n := len ( status . Stdout )
fmt . Println ( status . Stdout [ n - 1 ])
}
}()
// Stop command after 1 hour
go func () {
<- time . After ( 1 * time . Hour )
findCmd . Stop ()
}()
// Check if command is done
select {
case finalStatus := <- statusChan :
// done
default :
// no, still running
}
// Block waiting for command to exit, be stopped, or be killed
finalStatus := <- statusChan
}
就是這樣,只有三個方法: Start
、 Stop
和Status
。如果可能,最好使用go-cmd/Cmd
而不是os/exec.Cmd
因為go-cmd/Cmd
提供:
如上例所示,啟動指令會立即傳回一個頻道,當指令因任何原因退出時,最終狀態會傳送到該頻道。因此,預設情況下命令非同步運行,但同步運行也是可能且簡單的:
// Run foo and block waiting for it to exit
c := cmd . NewCmd ( "foo" )
s := <- c . Start ()
要實現與os/exec.Cmd
類似的功能,需要此套件已完成的所有操作。
在命令運行時想要讀取 stdout 或 stderr 是很常見的。常見的方法是呼叫 StdoutPipe 並從提供的io.ReadCloser
中讀取。這是可行的,但它是錯誤的,因為它會導致競爭條件( go test -race
檢測到),並且文檔說這是錯誤的:
因此,在管道的所有讀取完成之前呼叫 Wait 是不正確的。基於同樣的原因,使用 StdoutPipe 時呼叫 Run 是不正確的。
正確的解決方案是設定Stdout
的io.Writer
。為了線程安全和非競爭,這需要進一步的編寫工作,同時可能需要 N 個 goroutine 進行讀取。 go-cmd/Cmd
已經完成了這項工作。
與即時 stdout 和 stderr 類似,很高興看到例如經過的運行時間。該套件允許: Status
可以由任何 goroutine 隨時調用,並且它返回以下結構:
type Status struct {
Cmd string
PID int
Complete bool
Exit int
Error error
Runtime float64 // seconds
Stdout [] string
Stderr [] string
}
說到上面的結構體,Go 內建的Cmd
並沒有將所有回傳資訊放在一個地方,這很好,因為 Go 太棒了!但為了節省一些時間, go-cmd/Cmd
使用上面的Status
結構來傳達有關命令的所有資訊。即使命令完成,呼叫Status
也會傳回最終狀態,與傳送到呼叫Start
返回的狀態通道的最終狀態相同。
os/exec/Cmd.Wait 即使在命令被終止後也可能阻塞。這可能會令人驚訝並導致問題。但go-cmd/Cmd.Stop
可靠地終止命令,這並不奇怪。該問題與進程組 ID 有關。終止命令 PID 是很常見的,但通常需要終止其進程組 ID。 go-cmd/Cmd.Stop
實現了必要的低階魔法來實現這一點。
除了 100% 測試覆蓋率和無競爭條件之外,該套件還積極用於生產環境。
Brian Ip 編寫了原始程式碼來獲取退出狀態。奇怪的是,Go 不只提供了這個,它還需要像exiterr.Sys().(syscall.WaitStatus)
等魔法。
麻省理工學院 © go-Cmd。