Redirigir la tubería de salida estándar del proceso hijo en Go

105

Estoy escribiendo un programa en Go que ejecuta un programa similar a un servidor (también Go). Ahora quiero tener la salida estándar del programa secundario en la ventana de mi terminal donde inicié el programa principal. Una forma de hacer esto es con la cmd.Output()función, pero esta imprime la salida estándar solo después de que el proceso haya finalizado. (Eso es un problema porque este programa similar a un servidor se ejecuta durante mucho tiempo y quiero leer la salida del registro)

La variable outes de type io.ReadClosery no sé qué debo hacer con ella para lograr mi tarea, y no puedo encontrar nada útil en la web sobre este tema.

func main() {
    cmd := exec.Command("/path/to/my/child/program")
    out, err := cmd.StdoutPipe()
    if err != nil {
        fmt.Println(err)
    }
    err = cmd.Start()
    if err != nil {
        fmt.Println(err)
    }
    //fmt.Println(out)
    cmd.Wait()
} 

Explicación del código: descomente la Printlnfunción para que el código se compile, sé que Println(out io.ReadCloser)no es una función significativa.
(produce la salida &{3 |0 <nil> 0}) Estas dos líneas solo son necesarias para que el código se compile.

mbert
fuente
1
Su línea "exec" de la declaración de importación debe ser "os / exec".
evilspacepirate
gracias por la información, en realidad solo fue exec pre go1, ahora está en el sistema operativo. actualizado para go1
mbert
1
No creo que realmente necesites llamar io.Copydentro de las rutinas de go
rmonjo
No creo que necesites llamar cmd.Wait()o el for{}bucle ... ¿por qué están aquí?
weberc2
@ weberc2 para este vistazo a la respuesta de elimisteve. El bucle for no es necesario si solo desea ejecutar el programa una vez. Pero si no llama a cmd.Wait (), su main () puede finalizar antes de que finalice su programa llamado, y no obtiene la salida que desea
mbert

Respuestas:

207

Ahora quiero tener la salida estándar del programa secundario en la ventana de mi terminal donde inicié el programa principal.

No es necesario meterse con tuberías o goroutines, este es fácil.

func main() {
    // Replace `ls` (and its arguments) with something more interesting
    cmd := exec.Command("ls", "-l")
    cmd.Stdout = os.Stdout
    cmd.Stderr = os.Stderr
    cmd.Run()
}
cmccabe
fuente
4
Además, si desea que el comando escuche la entrada, simplemente puede configurarlo cmd.Stdin = os.Stdincomo si hubiera ejecutado literalmente ese comando desde su shell.
Nucleon
4
Para aquellos que buscan redirigir a en loglugar de stdout, hay una respuesta aquí
Rick Smith
18

Creo que si importa ioy osy sustituir este:

//fmt.Println(out)

con este:

go io.Copy(os.Stdout, out)

(ver documentación paraio.Copy y paraos.Stdout ), hará lo que quieras. (Descargo de responsabilidad: no probado).

Por cierto, probablemente también querrá capturar el error estándar, utilizando el mismo enfoque que para la salida estándar, pero con cmd.StderrPipey os.Stderr.

ruakh
fuente
2
@mbert: había usado suficientes otros lenguajes, y había leído lo suficiente sobre Go, como para tener una corazonada de qué característica probablemente existiría para hacer esto, y aproximadamente en qué forma; luego solo tuve que revisar los paquetes de documentos relevantes (encontrados por Google) para confirmar que mi corazonada era correcta y encontrar los detalles necesarios. Las partes más difíciles fueron (1) encontrar cómo se llama la salida estándar ( os.Stdout) y (2) confirmar la premisa de que, si no llama cmd.StdoutPipe()en absoluto, la salida estándar va a la /dev/nullsalida estándar del proceso padre en lugar de hacerlo .
ruakh
15

Para aquellos que no necesitan esto en un bucle, pero les gustaría que la salida del comando se hiciera eco en la terminal sin tener que cmd.Wait()bloquear otras declaraciones:

package main

import (
    "fmt"
    "io"
    "log"
    "os"
    "os/exec"
)

func checkError(err error) {
    if err != nil {
        log.Fatalf("Error: %s", err)
    }
}

func main() {
    // Replace `ls` (and its arguments) with something more interesting
    cmd := exec.Command("ls", "-l")

    // Create stdout, stderr streams of type io.Reader
    stdout, err := cmd.StdoutPipe()
    checkError(err)
    stderr, err := cmd.StderrPipe()
    checkError(err)

    // Start command
    err = cmd.Start()
    checkError(err)

    // Don't let main() exit before our command has finished running
    defer cmd.Wait()  // Doesn't block

    // Non-blockingly echo command output to terminal
    go io.Copy(os.Stdout, stdout)
    go io.Copy(os.Stderr, stderr)

    // I love Go's trivial concurrency :-D
    fmt.Printf("Do other stuff here! No need to wait.\n\n")
}
eliminar
fuente
Menor para su información: (Obviamente) es posible que se pierda los resultados de las goroutines iniciadas si su "hacer otras cosas aquí" se completa más rápido que las goroutines. La salida de main () hará que las goroutines también terminen. por lo que podría no terminar generando un eco en el terminal si no espera a que termine el cmd.
galaktor