Redirecionar o pipe stdout do processo filho no Go

105

Estou escrevendo um programa em Go que executa um programa semelhante a um servidor (também Go). Agora, quero ter o stdout do programa filho na janela do terminal onde iniciei o programa pai. Uma maneira de fazer isso é com a cmd.Output()função, mas ela imprime o stdout somente depois que o processo é encerrado. (Isso é um problema porque este programa semelhante a um servidor é executado por um longo tempo e eu quero ler a saída do log)

A variável outé de type io.ReadClosere não sei o que devo fazer com ela para cumprir minha tarefa e não consigo encontrar nada de útil na web sobre esse assunto.

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

Explicação para o código: descomente a Printlnfunção para obter o código para compilar, eu sei que Println(out io.ReadCloser)não é uma função significativa.
(produz a saída &{3 |0 <nil> 0}) Essas duas linhas são necessárias apenas para obter o código para compilar.

mbert
fonte
1
Sua linha "exec" da instrução de importação deve ser "os / exec".
evilspacepirate
obrigado pela informação, na verdade era apenas exec pré go1, agora está no sistema operacional. atualizou para go1
mbert
1
Eu não acho que você realmente precise ligar io.Copydentro das rotinas go
rmonjo
Eu não acho que você precisa ligar cmd.Wait()ou fazer o for{}loop ... por que estão aqui?
weberc2
@ weberc2 para isso olhe para a resposta de elimisteve. O loop for não é necessário se você deseja executar o programa apenas uma vez. Mas se você não chamar cmd.Wait (), seu main () pode terminar antes que o programa chamado termine, e você não obterá a saída que deseja
mbert

Respostas:

207

Agora, quero ter o stdout do programa filho na janela do terminal onde iniciei o programa pai.

Não há necessidade de mexer com canos ou goroutines, este é 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
fonte
4
Além disso, se você quiser que o comando ouça a entrada, pode simplesmente definir cmd.Stdin = os.Stdin, tornando-o como se tivesse literalmente executado esse comando a partir do seu shell.
Nucleon
4
Para aqueles que procuram redirecionar para em logvez de stdout, há uma resposta aqui
Rick Smith
18

Eu acredito que se você importar ioe ose substituir este:

//fmt.Println(out)

com isso:

go io.Copy(os.Stdout, out)

(consulte a documentação paraio.Copy e paraos.Stdout ), ele fará o que você quiser. (Isenção de responsabilidade: não testado.)

A propósito, provavelmente você também desejará capturar o erro padrão, usando a mesma abordagem que para a saída padrão, mas com cmd.StderrPipee os.Stderr.

Ruakh
fonte
2
@mbert: Eu já havia usado outras linguagens e lido o suficiente sobre Go para ter um palpite de qual recurso provavelmente existiria para fazer isso e de que forma aproximadamente; então eu só tive que olhar através dos documentos do pacote relevantes (encontrados no Google) para confirmar que meu palpite estava correto e para encontrar os detalhes necessários. As partes mais difíceis foram (1) encontrar qual saída padrão é chamada ( os.Stdout) e (2) confirmar a premissa de que, se você não chamar cmd.StdoutPipe(), a saída padrão vai para /dev/nulla saída padrão do processo pai .
ruakh
15

Para aqueles que não precisam disso em um loop, mas gostariam que a saída do comando ecoasse no terminal sem cmd.Wait()bloquear outras instruções:

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
fonte
Menor informação: (Obviamente) você pode perder os resultados dos goroutines iniciados se o seu "faça outras coisas aqui" for concluído mais rápido do que os goroutines. A saída de main () fará com que as goroutines terminem também. portanto, você poderia potencialmente não acabar realmente produzindo para eco no terminal se não esperar o cmd terminar.
galaktor