Przekieruj potok wyjściowy procesu potomnego w Go

105

Piszę program w Go, który wykonuje program podobny do serwera (także Go). Teraz chcę mieć standardowe wyjście programu potomnego w moim oknie terminala, w którym uruchomiłem program nadrzędny. Jednym ze sposobów jest użycie cmd.Output()funkcji, ale wypisuje ona standardowe wyjście dopiero po zakończeniu procesu. (To problem, ponieważ ten program podobny do serwera działa przez długi czas i chcę odczytać dane wyjściowe dziennika)

Zmienna outma wartość type io.ReadCloseri nie wiem, co mam z nią zrobić, aby wykonać swoje zadanie, i nie mogę znaleźć w sieci nic pomocnego na ten temat.

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

Wyjaśnienie do kodu: odkomentuj Printlnfunkcję, aby uzyskać kod do skompilowania, wiem, że Println(out io.ReadCloser)nie jest to znacząca funkcja.
(generuje wynik &{3 |0 <nil> 0}) Te dwa wiersze są potrzebne tylko do pobrania kodu do kompilacji.

mbert
źródło
1
Twój wiersz „exec” instrukcji importu powinien mieć postać „os / exec”.
evilspacepirate
dzięki za informację, właściwie był to tylko exec przed go1, teraz jest w systemie operacyjnym. zaktualizowałem go dla go1
mbert
1
Nie sądzę, że faktycznie musisz dzwonić io.Copyw ramach procedur go
rmonjo
Myślę, że nie musisz dzwonić cmd.Wait()ani for{}pętli ... dlaczego te tutaj są?
weberc2
@ weberc2, aby spojrzeć na odpowiedź elimisteve. Pętla for nie jest potrzebna, jeśli chcesz tylko raz uruchomić program. Ale jeśli nie nazywamy cmd.Wait (), twój main () może zakończyć się przed swoimi zwanych wykończeń programowych, a nie dostać wyjście chcesz
mbert

Odpowiedzi:

207

Teraz chcę mieć standardowe wyjście programu potomnego w moim oknie terminala, w którym uruchomiłem program nadrzędny.

Nie musisz majstrować przy rurach ani gorutynach, ten jest łatwy.

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
źródło
4
Ponadto, jeśli chcesz, aby polecenie nasłuchiwało danych wejściowych, możesz po prostu ustawić cmd.Stdin = os.Stdinje tak, jakbyś dosłownie wykonał to polecenie z powłoki.
Nucleon
4
Dla tych, którzy chcą przekierować logzamiast standardowego wyjścia, jest tutaj
Rick Smith,
18

Wierzę, że jeśli import ioi osi to wymienić:

//fmt.Println(out)

z tym:

go io.Copy(os.Stdout, out)

(zobacz dokumentację dlaio.Copy i dlaos.Stdout ), zrobi to, co chcesz. (Zastrzeżenie: nie testowane.)

Nawiasem mówiąc, prawdopodobnie będziesz chciał również przechwytywać standardowe błędy, używając tego samego podejścia, co w przypadku standardowego wyjścia, ale z cmd.StderrPipei os.Stderr.

ruakh
źródło
2
@mbert: Użyłem wystarczająco dużo innych języków i przeczytałem wystarczająco dużo o Go, aby mieć przeczucie, jaka funkcja prawdopodobnie będzie to robić iw jakiej mniej więcej formie; następnie musiałem tylko przejrzeć odpowiednią dokumentację pakietów (znalezioną przez Googling), aby potwierdzić, że moje przeczucie jest poprawne i znaleźć niezbędne szczegóły. Najtrudniejsze było (1) znalezienie, jak nazywa się standardowe wyjście ( os.Stdout) i (2) potwierdzenie założenia, że ​​jeśli w ogóle nie wywołasz cmd.StdoutPipe(), standardowe wyjście trafia /dev/nullraczej do standardowego wyjścia procesu nadrzędnego niż do standardowego wyjścia. .
ruakh
15

Dla tych, którzy nie potrzebują tego w pętli, ale chcieliby, aby wyjście polecenia było echem do terminala bez cmd.Wait()blokowania innych instrukcji:

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
źródło
Minor fyi: (Oczywiście) możesz przegapić wyniki rozpoczętych gorutyn, jeśli twoje "robienie tutaj innych rzeczy" kończy się szybciej niż gorutiny. Wyjście z main () spowoduje również zakończenie działania gorutyn. więc potencjalnie nie możesz skończyć z wyświetlaniem echa w terminalu, jeśli nie będziesz czekać na zakończenie cmd.
galaktor