Este pacote é um wrapper pequeno, mas muito útil, para os/exec.Cmd que torna seguro e simples a execução de comandos externos em aplicativos altamente simultâneos, assíncronos e em tempo real. Funciona em Linux, macOS e Windows. Aqui está o uso básico:
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
}
É isso, apenas três métodos: Start
, Stop
e Status
. Quando possível, é melhor usar go-cmd/Cmd
do que os/exec.Cmd
porque go-cmd/Cmd
fornece:
Como mostra o exemplo acima, iniciar um comando retorna imediatamente um canal para o qual o status final é enviado quando o comando é encerrado por qualquer motivo. Portanto, por padrão, os comandos são executados de forma assíncrona, mas a execução síncrona também é possível e fácil:
// Run foo and block waiting for it to exit
c := cmd . NewCmd ( "foo" )
s := <- c . Start ()
Para conseguir algo semelhante com os/exec.Cmd
é necessário tudo o que este pacote já faz.
É comum querer ler stdout ou stderr enquanto o comando está em execução. A abordagem comum é chamar StdoutPipe e ler o io.ReadCloser
fornecido. Isso funciona, mas está errado porque causa uma condição de corrida (que go test -race
detecta) e os documentos dizem que está errado:
Portanto, é incorreto chamar Wait antes que todas as leituras do pipe sejam concluídas. Pelo mesmo motivo, é incorreto chamar Run ao usar StdoutPipe.
A solução adequada é definir io.Writer
de Stdout
. Para ser thread-safe e não arriscado, isso requer mais trabalho para escrever enquanto possivelmente N-muitos goroutines são lidos. go-cmd/Cmd
fez esse trabalho.
Semelhante ao stdout e ao stderr em tempo real, é bom ver, por exemplo, o tempo de execução decorrido. Este pacote permite que: Status
possa ser chamado a qualquer momento por qualquer goroutine e retorne esta estrutura:
type Status struct {
Cmd string
PID int
Complete bool
Exit int
Error error
Runtime float64 // seconds
Stdout [] string
Stderr [] string
}
Falando na estrutura acima, Cmd
integrado do Go não coloca todas as informações de retorno em um só lugar, o que é bom porque o Go é incrível! Mas para economizar tempo, go-cmd/Cmd
usa a estrutura Status
acima para transmitir todas as informações sobre o comando. Mesmo quando o comando termina, a chamada de Status
retorna o status final, o mesmo status final enviado ao canal de status retornado pela chamada para Start
.
os/exec/Cmd.Wait pode bloquear mesmo depois que o comando for eliminado. Isso pode ser surpreendente e causar problemas. Mas go-cmd/Cmd.Stop
encerra o comando de forma confiável, sem surpresas. O problema tem a ver com IDs de grupos de processos. É comum eliminar o PID do comando, mas geralmente é necessário eliminar o ID do grupo de processos. go-cmd/Cmd.Stop
implementa a mágica de baixo nível necessária para que isso aconteça.
Além de 100% de cobertura de teste e sem condições de corrida, este pacote é usado ativamente em ambientes de produção.
Brian Ip escreveu o código original para obter o status de saída. Estranhamente, Go não apenas fornece isso, mas também requer mágica como exiterr.Sys().(syscall.WaitStatus)
e muito mais.
MIT © go-Cmd.