这个包是 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。