Go by Example: Spawning Processes

A veces nuestros programas en Go necesitan generar otros procesos que no son de Go.

package main
import (
    "fmt"
    "io"
    "os/exec"
)
func main() {

Comenzaremos con un comando simple que no toma argumentos ni entrada y simplemente imprime algo en stdout. El ayudante exec.Command crea un objeto para representar este proceso externo.

    dateCmd := exec.Command("date")

El método Output ejecuta el comando, espera a que termine y recoge su salida estándar. Si no hubo errores, dateOut contendrá bytes con la información de la fecha.

    dateOut, err := dateCmd.Output()
    if err != nil {
        panic(err)
    }
    fmt.Println("> date")
    fmt.Println(string(dateOut))

Output y otros métodos de Command devolverán *exec.Error si hubo un problema al ejecutar el comando (por ejemplo, ruta incorrecta), y *exec.ExitError si el comando se ejecutó pero terminó con un código de retorno distinto de cero.

    _, err = exec.Command("date", "-x").Output()
    if err != nil {
        switch e := err.(type) {
        case *exec.Error:
            fmt.Println("failed executing:", err)
        case *exec.ExitError:
            fmt.Println("command exit rc =", e.ExitCode())
        default:
            panic(err)
        }
    }

A continuación, veremos un caso un poco más complicado donde canalizamos datos al proceso externo en su stdin y recogemos los resultados de su stdout.

    grepCmd := exec.Command("grep", "hello")

Aquí tomamos explícitamente tuberías de entrada/salida, iniciamos el proceso, escribimos algo de entrada en él, leemos la salida resultante y finalmente esperamos a que el proceso termine.

    grepIn, _ := grepCmd.StdinPipe()
    grepOut, _ := grepCmd.StdoutPipe()
    grepCmd.Start()
    grepIn.Write([]byte("hello grep\ngoodbye grep"))
    grepIn.Close()
    grepBytes, _ := io.ReadAll(grepOut)
    grepCmd.Wait()

Omitimos las comprobaciones de errores en el ejemplo anterior, pero podrías usar el patrón habitual if err != nil para todas ellas. También solo recogemos los resultados de StdoutPipe, pero podrías recoger los de StderrPipe de la misma manera.

    fmt.Println("> grep hello")
    fmt.Println(string(grepBytes))

Ten en cuenta que al generar comandos necesitamos proporcionar un comando y un arreglo de argumentos explícitamente delimitados, en vez de poder pasar solo una cadena de línea de comandos. Si quieres generar un comando completo con una cadena, puedes usar la opción -c de bash:

    lsCmd := exec.Command("bash", "-c", "ls -a -l -h")
    lsOut, err := lsCmd.Output()
    if err != nil {
        panic(err)
    }
    fmt.Println("> ls -a -l -h")
    fmt.Println(string(lsOut))
}

Los programas generados devuelven una salida que es la misma que si los hubiéramos ejecutado directamente desde la línea de comandos.

$ go run spawning-processes.go 
> date
Thu 05 May 2022 10:10:12 PM PDT

date no tiene una bandera -x por lo que saldrá con un mensaje de error y un código de retorno distinto de cero.

command exited with rc = 1
> grep hello
hello grep
> ls -a -l -h
drwxr-xr-x  4 mark 136B Oct 3 16:29 .
drwxr-xr-x 91 mark 3.0K Oct 3 12:50 ..
-rw-r--r--  1 mark 1.3K Oct 3 16:28 spawning-processes.go

Siguiente ejemplo: Exec'ing Processes.