Dieses Paket ist ein kleiner, aber sehr nützlicher Wrapper für os/exec.Cmd, der die sichere und einfache Ausführung externer Befehle in hochgradig gleichzeitigen, asynchronen Echtzeitanwendungen ermöglicht. Es funktioniert unter Linux, macOS und Windows. Hier ist die grundlegende Verwendung:
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
}
Das war's, nur drei Methoden: Start
, Stop
und Status
. Wenn möglich, ist es besser go-cmd/Cmd
als os/exec.Cmd
zu verwenden, da go-cmd/Cmd
Folgendes bietet:
Wie das obige Beispiel zeigt, wird beim Starten eines Befehls sofort ein Kanal zurückgegeben, an den der endgültige Status gesendet wird, wenn der Befehl aus irgendeinem Grund beendet wird. Daher werden Befehle standardmäßig asynchron ausgeführt, aber auch die synchrone Ausführung ist möglich und einfach:
// Run foo and block waiting for it to exit
c := cmd . NewCmd ( "foo" )
s := <- c . Start ()
Um mit os/exec.Cmd
Ähnliches zu erreichen, ist alles erforderlich, was dieses Paket bereits leistet.
Es kommt häufig vor, dass man stdout oder stderr lesen möchte, während der Befehl ausgeführt wird. Der übliche Ansatz besteht darin, StdoutPipe aufzurufen und aus dem bereitgestellten io.ReadCloser
zu lesen. Das funktioniert, ist aber falsch, weil es eine Race-Bedingung verursacht (die von go test -race
erkannt wird) und die Dokumente sagen, dass es falsch ist:
Es ist daher falsch, Wait aufzurufen, bevor alle Lesevorgänge aus der Pipe abgeschlossen sind. Aus dem gleichen Grund ist es falsch, Run aufzurufen, wenn StdoutPipe verwendet wird.
Die richtige Lösung besteht darin, den io.Writer
von Stdout
festzulegen. Um Thread-sicher und fehlerfrei zu sein, ist weitere Schreibarbeit erforderlich, während möglicherweise N-viele Goroutinen lesen. go-cmd/Cmd
hat diese Arbeit erledigt.
Ähnlich wie bei stdout und stderr in Echtzeit ist es schön, beispielsweise die verstrichene Laufzeit zu sehen. Dieses Paket ermöglicht Folgendes: Status
kann jederzeit von jeder Goroutine aufgerufen werden und gibt diese Struktur zurück:
type Status struct {
Cmd string
PID int
Complete bool
Exit int
Error error
Runtime float64 // seconds
Stdout [] string
Stderr [] string
}
Apropos Struktur oben: Der in Go integrierte Cmd
speichert nicht alle Rückgabeinformationen an einem Ort, was in Ordnung ist, denn Go ist großartig! Um jedoch etwas Zeit zu sparen, verwendet go-cmd/Cmd
die obige Status
, um alle Informationen über den Befehl zu übermitteln. Selbst wenn der Befehl beendet ist, gibt der Aufruf von Status
den endgültigen Status zurück, den gleichen endgültigen Status, der an den Statuskanal gesendet wird, der durch den Aufruf von Start
zurückgegeben wurde.
os/exec/Cmd.Wait kann auch nach dem Abbruch des Befehls blockieren. Das kann überraschend sein und Probleme verursachen. Aber go-cmd/Cmd.Stop
beendet den Befehl zuverlässig, keine Überraschungen. Das Problem hat mit Prozessgruppen-IDs zu tun. Es ist üblich, die Befehls-PID zu löschen, aber normalerweise muss stattdessen die Prozessgruppen-ID gelöscht werden. go-cmd/Cmd.Stop
implementiert die notwendige Magie auf niedriger Ebene, um dies zu ermöglichen.
Neben 100 % Testabdeckung und keinen Race Conditions wird dieses Paket aktiv in Produktionsumgebungen eingesetzt.
Brian Ip hat den Originalcode geschrieben, um den Exit-Status abzurufen. Seltsamerweise bietet Go dies nicht nur, es erfordert auch Magie wie exiterr.Sys().(syscall.WaitStatus)
und mehr.
MIT © go-Cmd.