Este paquete es un contenedor pequeño pero muy útil para os/exec.Cmd que hace que sea seguro y sencillo ejecutar comandos externos en aplicaciones altamente concurrentes, asincrónicas y en tiempo real. Funciona en Linux, macOS y Windows. Aquí está el 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
}
Eso es todo, sólo tres métodos: Start
, Stop
y Status
. Cuando sea posible, es mejor usar go-cmd/Cmd
que os/exec.Cmd
porque go-cmd/Cmd
proporciona:
Como muestra el ejemplo anterior, iniciar un comando devuelve inmediatamente un canal al que se envía el estado final cuando el comando sale por cualquier motivo. Entonces, de forma predeterminada, los comandos se ejecutan de forma asincrónica, pero la ejecución sincrónica también es posible y sencilla:
// Run foo and block waiting for it to exit
c := cmd . NewCmd ( "foo" )
s := <- c . Start ()
Para lograr algo similar con os/exec.Cmd
se requiere todo lo que este paquete ya hace.
Es común querer leer stdout o stderr mientras se ejecuta el comando. El enfoque común es llamar a StdoutPipe y leer desde el io.ReadCloser
proporcionado. Esto funciona, pero está mal porque provoca una condición de carrera (que go test -race
detecta) y los documentos dicen que está mal:
Por lo tanto, es incorrecto llamar a Wait antes de que se hayan completado todas las lecturas de la tubería. Por la misma razón, es incorrecto llamar a Ejecutar cuando se usa StdoutPipe.
La solución adecuada es configurar io.Writer
de Stdout
. Para ser seguro para subprocesos y no competitivo, esto requiere más trabajo para escribir mientras posiblemente se lean N-muchas rutinas. go-cmd/Cmd
ha hecho este trabajo.
De manera similar a stdout y stderr en tiempo real, es bueno ver, por ejemplo, el tiempo de ejecución transcurrido. Este paquete permite que: Cualquier rutina pueda llamar Status
en cualquier momento y devuelve esta estructura:
type Status struct {
Cmd string
PID int
Complete bool
Exit int
Error error
Runtime float64 // seconds
Stdout [] string
Stderr [] string
}
Hablando de la estructura anterior, Cmd
integrado de Go no coloca toda la información de devolución en un solo lugar, lo cual está bien porque Go es increíble. Pero para ahorrar algo de tiempo, go-cmd/Cmd
usa la estructura Status
anterior para transmitir toda la información sobre el comando. Incluso cuando finaliza el comando, llamar Status
devuelve el estado final, el mismo estado final enviado al canal de estado devuelto por la llamada a Start
.
os/exec/Cmd.Wait puede bloquearse incluso después de que se elimine el comando. Esto puede resultar sorprendente y causar problemas. Pero go-cmd/Cmd.Stop
termina el comando de manera confiable, sin sorpresas. El problema tiene que ver con los ID de los grupos de procesos. Es común eliminar el comando PID, pero generalmente es necesario eliminar su ID de grupo de procesos. go-cmd/Cmd.Stop
implementa la magia de bajo nivel necesaria para que esto suceda.
Además de una cobertura de prueba del 100% y sin condiciones de carrera, este paquete se utiliza activamente en entornos de producción.
Brian Ip escribió el código original para obtener el estado de salida. Curiosamente, Go no sólo proporciona esto, sino que requiere magia como exiterr.Sys().(syscall.WaitStatus)
y más.
MIT © go-Cmd.