Reindirizza la pipe stdout del processo figlio in Go


105

Sto scrivendo un programma in Go che esegue un programma simile a un server (anche Go). Ora voglio avere lo stdout del programma figlio nella mia finestra del terminale dove ho avviato il programma genitore. Un modo per farlo è con la cmd.Output()funzione, ma questa stampa lo stdout solo dopo che il processo è terminato. (Questo è un problema perché questo programma simile a un server viene eseguito per molto tempo e voglio leggere l'output del registro)

La variabile outè di type io.ReadClosere non so cosa dovrei farne per svolgere il mio compito, e non riesco a trovare nulla di utile sul web su questo argomento.

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

Spiegazione del codice: rimuovere il commento dalla Printlnfunzione per compilare il codice, so che Println(out io.ReadCloser)non è una funzione significativa.
(produce l'output &{3 |0 <nil> 0}) Queste due righe sono necessarie solo per ottenere il codice da compilare.


1
La riga "exec" dell'istruzione import dovrebbe essere "os / exec".
evilspacepirate

grazie per le informazioni, in realtà era solo exec pre go1, ora è in os. aggiornato per go1
mbert

1
Non penso che tu abbia effettivamente bisogno di chiamare io.Copyall'interno delle routine go
rmonjo

Non penso che tu abbia bisogno di chiamare cmd.Wait()o fare il for{}loop ... perché sono qui?
weberc2

@ weberc2 per questo guarda in basso alla risposta di elimisteve. Il ciclo for non è necessario se si desidera eseguire il programma una sola volta. Ma se non chiami cmd.Wait (), il tuo main () potrebbe terminare prima che il programma chiamato finisca e non ottieni l'output che desideri
mbert

Risposte:


207

Ora voglio avere lo stdout del programma figlio nella mia finestra del terminale dove ho avviato il programma genitore.

Non c'è bisogno di scherzare con pipe o goroutine, questo è 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()
}

4
Inoltre, se si desidera che il comando ascolti l'input, è possibile semplicemente impostarlo in cmd.Stdin = os.Stdinmodo che sia come se si fosse letteralmente eseguito quel comando dalla propria shell.
Nucleon

4
Per coloro che desiderano reindirizzare a loginvece che a stdout, c'è una risposta qui
Rick Smith

18

Credo che se si importa ioe ose sostituire questo:

//fmt.Println(out)

con questo:

go io.Copy(os.Stdout, out)

(vedi la documentazione perio.Copy e peros.Stdout ), farà quello che vuoi. (Disclaimer: non testato.)

A proposito, probabilmente vorrai catturare anche l'errore standard, usando lo stesso approccio dell'output standard, ma con cmd.StderrPipee os.Stderr.


2
@mbert: avevo usato abbastanza altre lingue e avevo letto abbastanza su Go, per avere un'idea di quale caratteristica sarebbe probabilmente esistita per farlo, e in quale forma approssimativa; quindi ho dovuto solo guardare attraverso i relativi pacchetti-doc (trovati da Google) per confermare che la mia intuizione fosse corretta e per trovare i dettagli necessari. Le parti più difficili sono state (1) trovare quello che viene chiamato lo standard output ( os.Stdout) e (2) confermare la premessa che, se non si chiama cmd.StdoutPipe()affatto, lo standard output va a /dev/nullpiuttosto che allo standard output del processo genitore .
ruakh

15

Per coloro che non ne hanno bisogno in un ciclo, ma vorrebbero che l'output del comando echeggi nel terminale senza dover cmd.Wait()bloccare altre istruzioni:

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

Minor fyi: (Ovviamente) potresti perdere i risultati delle goroutine iniziate se il tuo "fai altre cose qui" si completa più velocemente delle goroutine. L'uscita main () farà terminare anche le goroutine. quindi potresti potenzialmente non finire per visualizzare effettivamente l'output nel terminale se non aspetti che il cmd finisca.
galaktor
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.