Stdout-Pipe des untergeordneten Prozesses in Go umleiten

105

Ich schreibe ein Programm in Go, das ein serverähnliches Programm ausführt (auch Go). Jetzt möchte ich die Standardausgabe des untergeordneten Programms in meinem Terminalfenster haben, in dem ich das übergeordnete Programm gestartet habe. Eine Möglichkeit, dies zu tun, ist die cmd.Output()Funktion, die jedoch erst nach dem Beenden des Prozesses den Standard ausgibt. (Das ist ein Problem, da dieses serverähnliche Programm lange läuft und ich die Protokollausgabe lesen möchte.)

Die Variable outist von type io.ReadCloserund ich weiß nicht, was ich damit machen soll, um meine Aufgabe zu erfüllen, und ich kann im Web zu diesem Thema nichts Hilfreiches finden.

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

Erklärung zum Code: Kommentieren Sie die PrintlnFunktion aus, um den Code zum Kompilieren zu bringen. Ich weiß, dass dies Println(out io.ReadCloser)keine sinnvolle Funktion ist.
(Es erzeugt die Ausgabe. &{3 |0 <nil> 0}) Diese beiden Zeilen sind nur erforderlich, damit der Code kompiliert werden kann.

mbert
quelle
1
Ihre "exec" -Zeile der import-Anweisung sollte "os / exec" sein.
evilspacepirate
danke für die info, eigentlich war es nur exec pre go1, jetzt ist es in os. aktualisiert es für go1
mbert
1
Ich glaube nicht, dass Sie tatsächlich io.Copyinnerhalb von Go-Routinen
anrufen müssen
Ich glaube nicht, dass Sie anrufen müssen cmd.Wait()oder die for{}Schleife ... warum sind diese hier?
weberc2
@ weberc2 für diesen Blick auf die Antwort von elimisteve. Die for-Schleife wird nicht benötigt, wenn Sie das Programm nur einmal ausführen möchten. Wenn Sie jedoch nicht cmd.Wait () aufrufen, endet Ihr main () möglicherweise, bevor das aufgerufene Programm beendet ist, und Sie erhalten nicht die gewünschte Ausgabe
mbert

Antworten:

207

Jetzt möchte ich die Standardausgabe des untergeordneten Programms in meinem Terminalfenster haben, in dem ich das übergeordnete Programm gestartet habe.

Sie müssen sich nicht mit Rohren oder Goroutinen herumschlagen, dies ist einfach.

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
quelle
4
Wenn Sie möchten, dass der Befehl auf Eingaben wartet, können Sie ihn einfach so einstellen cmd.Stdin = os.Stdin, als hätten Sie diesen Befehl buchstäblich über Ihre Shell ausgeführt.
Nucleon
4
Für diejenigen, die auf die Umleitung logstatt stdout, gibt es eine Antwort hier
Rick Smith
18

Ich glaube , dass , wenn Sie importieren iound osund diese ersetzen:

//fmt.Println(out)

mit diesem:

go io.Copy(os.Stdout, out)

(siehe Dokumentation fürio.Copy und füros.Stdout ), es wird tun, was Sie wollen. (Haftungsausschluss: nicht getestet.)

Übrigens möchten Sie wahrscheinlich auch Standardfehler erfassen, indem Sie denselben Ansatz wie für die Standardausgabe verwenden, jedoch mit cmd.StderrPipeund os.Stderr.

Ruakh
quelle
2
@mbert: Ich hatte genug andere Sprachen verwendet und genug über Go gelesen, um eine Vorstellung davon zu haben, welche Funktion dies wahrscheinlich geben würde und in welcher Form; dann musste ich nur noch die relevanten Paketdokumente (gefunden von Googling) durchsehen, um zu bestätigen, dass meine Vermutung richtig war, und um die notwendigen Details zu finden. Am schwierigsten war es, (1) herauszufinden, was als Standardausgabe bezeichnet wird ( os.Stdout) und (2) die Prämisse zu bestätigen, dass cmd.StdoutPipe()die Standardausgabe , wenn Sie überhaupt nicht aufrufen , /dev/nulleher zur Standardausgabe des übergeordneten Prozesses geht .
Ruakh
15

Für diejenigen, die dies nicht in einer Schleife benötigen, aber möchten, dass die Befehlsausgabe in das Terminal zurückgesendet wird, ohne dass cmd.Wait()andere Anweisungen blockiert werden:

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")
}
elimisteve
quelle
Kleinere Informationen: (Offensichtlich) Sie könnten die Ergebnisse der gestarteten Goroutinen verpassen, wenn Ihr "andere Dinge hier tun" schneller als die Goroutinen abgeschlossen wird. Durch das Beenden von main () werden auch die Goroutinen beendet. Wenn Sie nicht darauf warten, dass der Cmd fertig ist, können Sie möglicherweise nicht tatsächlich im Echo ausgeben.
Galaktor