Rediriger le tube stdout du processus enfant dans Go

105

J'écris un programme en Go qui exécute un programme de type serveur (également Go). Maintenant, je veux avoir le stdout du programme enfant dans ma fenêtre de terminal où j'ai démarré le programme parent. Une façon de le faire est d'utiliser la cmd.Output()fonction, mais cela n'imprime le stdout qu'une fois le processus terminé. (C'est un problème car ce programme de type serveur fonctionne pendant longtemps et je souhaite lire la sortie du journal)

La variable outest de type io.ReadCloseret je ne sais pas ce que je dois en faire pour accomplir ma tâche, et je ne trouve rien d'utile sur le Web à ce sujet.

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()
} 

Explication au code: décommentez la Printlnfonction pour obtenir le code à compiler, je sais que ce Println(out io.ReadCloser)n'est pas une fonction significative.
(il produit la sortie &{3 |0 <nil> 0}) Ces deux lignes sont juste nécessaires pour obtenir le code à compiler.

mbert
la source
1
Votre ligne "exec" de l'instruction d'importation doit être "os / exec".
evilspacepirate
merci pour l'info, en fait c'était seulement exec pre go1, maintenant c'est dans os. mis à jour pour go1
mbert
1
Je ne pense pas que vous ayez réellement besoin d'appeler io.Copydans les routines aller
rmonjo
Je ne pense pas que vous ayez besoin d'appeler cmd.Wait()ou de faire la for{}boucle ... pourquoi sont-ils ici?
weberc2
@ weberc2 pour ce regard vers la réponse d'élimisteve. La boucle for n'est pas nécessaire si vous souhaitez simplement exécuter le programme une fois. Mais si vous n'appelez pas cmd.Wait (), votre main () peut se terminer avant la fin du programme appelé, et vous n'obtenez pas le résultat souhaité
mbert

Réponses:

207

Maintenant, je veux avoir le stdout du programme enfant dans ma fenêtre de terminal où j'ai démarré le programme parent.

Pas besoin de jouer avec des pipes ou des goroutines, celui-ci est facile.

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
la source
4
De plus, si vous voulez que la commande écoute les entrées, vous pouvez simplement définir cmd.Stdin = os.Stdince qui fait comme si vous aviez littéralement exécuté cette commande à partir de votre shell.
Nucleon
4
Pour ceux qui cherchent à rediriger vers logau lieu de stdout, il y a une réponse ici
Rick Smith
18

Je crois que si vous importez ioet oset remplacer ceci:

//fmt.Println(out)

avec ça:

go io.Copy(os.Stdout, out)

(voir documentation pourio.Copy et pouros.Stdout ), il fera ce que vous voulez. (Avertissement: non testé.)

Au fait, vous voudrez probablement aussi capturer l'erreur standard, en utilisant la même approche que pour la sortie standard, mais avec cmd.StderrPipeet os.Stderr.

Ruakh
la source
2
@mbert: J'avais utilisé suffisamment d'autres langages, et j'en avais suffisamment lu sur Go, pour avoir une idée de la fonctionnalité qui existerait probablement pour le faire, et sous quelle forme approximativement; Ensuite, j'ai juste eu à parcourir les documents de package pertinents (trouvés par Google) pour confirmer que mon intuition était correcte et pour trouver les détails nécessaires. Les parties les plus difficiles étaient (1) de trouver ce que l'on appelle la sortie standard ( os.Stdout) et (2) de confirmer la prémisse que, si vous n'appelez pas cmd.StdoutPipe()du tout, la sortie standard va /dev/nullplutôt que vers la sortie standard du processus parent. .
ruakh
15

Pour ceux qui n'en ont pas besoin dans une boucle, mais qui aimeraient que la sortie de la commande fasse écho dans le terminal sans avoir à cmd.Wait()bloquer d'autres instructions:

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")
}
éliminer
la source
Minor fyi: (De toute évidence) vous pourriez manquer les résultats des goroutines commencées si votre "faire autre chose ici" se termine plus rapidement que les goroutines. La sortie main () entraînera également la fin des goroutines. vous pourriez donc ne pas finir par produire un écho dans le terminal si vous n'attendez pas que la cmd se termine.
galaktor