Ce package est un wrapper petit mais très utile autour de os/exec.Cmd qui permet d'exécuter de manière sûre et simple des commandes externes dans des applications en temps réel hautement concurrentes, asynchrones. Cela fonctionne sous Linux, macOS et Windows. Voici l'utilisation de base :
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
}
Voilà, seulement trois méthodes : Start
, Stop
et Status
. Lorsque cela est possible, il est préférable d'utiliser go-cmd/Cmd
plutôt que os/exec.Cmd
car go-cmd/Cmd
fournit :
Comme le montre l'exemple ci-dessus, le démarrage d'une commande renvoie immédiatement un canal auquel l'état final est envoyé lorsque la commande se termine pour une raison quelconque. Ainsi, par défaut, les commandes s'exécutent de manière asynchrone, mais l'exécution de manière synchrone est également possible et simple :
// Run foo and block waiting for it to exit
c := cmd . NewCmd ( "foo" )
s := <- c . Start ()
Pour obtenir un résultat similaire avec os/exec.Cmd
il faut tout ce que ce package fait déjà.
Il est courant de vouloir lire stdout ou stderr pendant l' exécution de la commande. L'approche courante consiste à appeler StdoutPipe et à lire à partir du io.ReadCloser
fourni. Cela fonctionne mais c'est faux car cela provoque une condition de concurrence (qui go test -race
détecte) et la documentation dit que c'est faux :
Il est donc incorrect d'appeler Wait avant que toutes les lectures du canal ne soient terminées. Pour la même raison, il est incorrect d'appeler Run lors de l'utilisation de StdoutPipe.
La bonne solution consiste à définir le io.Writer
de Stdout
. Pour être thread-safe et non racé, cela nécessite un travail supplémentaire pour écrire pendant qu'éventuellement N-plusieurs goroutines lisent. go-cmd/Cmd
a fait ce travail.
Semblable à stdout et stderr en temps réel, il est agréable de voir, par exemple, le temps d'exécution écoulé. Ce package permet que : Status
puisse être appelé à tout moment par n'importe quelle goroutine, et il renvoie cette structure :
type Status struct {
Cmd string
PID int
Complete bool
Exit int
Error error
Runtime float64 // seconds
Stdout [] string
Stderr [] string
}
En parlant de cette structure ci-dessus, Go intégré Cmd
ne met pas toutes les informations de retour au même endroit, ce qui est bien car Go est génial ! Mais pour gagner du temps, go-cmd/Cmd
utilise la structure Status
ci-dessus pour transmettre toutes les informations sur la commande. Même lorsque la commande se termine, l'appel de Status
renvoie le statut final, le même statut final envoyé au canal d'état renvoyé par l'appel à Start
.
os/exec/Cmd.Wait peut bloquer même après la suppression de la commande. Cela peut surprendre et poser des problèmes. Mais go-cmd/Cmd.Stop
termine la commande de manière fiable, sans surprise. Le problème concerne les ID de groupe de processus. Il est courant de supprimer la commande PID, mais il faut généralement supprimer son ID de groupe de processus à la place. go-cmd/Cmd.Stop
implémente la magie de bas niveau nécessaire pour que cela se produise.
En plus d'une couverture de test à 100 % et d'une absence de conditions de concurrence, ce package est activement utilisé dans les environnements de production.
Brian Ip a écrit le code original pour obtenir le statut de sortie. Étrangement, Go ne fournit pas seulement cela, il nécessite de la magie comme exiterr.Sys().(syscall.WaitStatus)
et plus encore.
MIT © go-Cmd.