Alihkan pipa stdout dari proses anak di Go

105

Saya sedang menulis program di Go yang menjalankan program seperti server (juga Go). Sekarang saya ingin memiliki stdout program anak di jendela terminal tempat saya memulai program induk. Salah satu cara untuk melakukannya adalah dengan cmd.Output()fungsi, tetapi ini mencetak stdout hanya setelah proses keluar. (Itu masalah karena program mirip server ini berjalan untuk waktu yang lama dan saya ingin membaca keluaran log)

Variabelnya outadalah dari type io.ReadCloserdan saya tidak tahu apa yang harus saya lakukan dengannya untuk menyelesaikan tugas saya, dan saya tidak dapat menemukan apa pun yang berguna di web tentang topik ini.

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

Penjelasan kode: hapus komentar Printlnfungsi untuk mendapatkan kode untuk dikompilasi, saya tahu itu Println(out io.ReadCloser)bukan fungsi yang berarti.
(itu menghasilkan output &{3 |0 <nil> 0}) Kedua baris ini hanya diperlukan untuk mendapatkan kode yang akan dikompilasi.

mbert
sumber
1
Baris "exec" dari pernyataan import harus "os / exec".
evilspacepirate
terima kasih untuk infonya, sebenarnya hanya exec pre go1, sekarang di os. memperbaruinya untuk go1
mbert
1
Saya tidak berpikir bahwa Anda benar-benar perlu menelepon io.Copydalam rutinitas go
rmonjo
Saya tidak berpikir Anda perlu menelepon cmd.Wait()atau for{}memutar ... mengapa ini ada di sini?
weberc2
@ weberc2 untuk tampilan ini ke jawaban elimisteve. Perulangan for tidak diperlukan jika Anda hanya ingin menjalankan program sekali. Tetapi jika Anda tidak memanggil cmd.Wait (), main () Anda mungkin berakhir sebelum program yang dipanggil selesai, dan Anda tidak mendapatkan output yang Anda inginkan
mbert

Jawaban:

207

Sekarang saya ingin memiliki stdout program anak di jendela terminal tempat saya memulai program induk.

Tidak perlu repot dengan pipa atau goroutine, ini mudah.

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.dll
sumber
4
Selain itu, jika Anda ingin perintah untuk mendengarkan input, Anda dapat mengaturnya cmd.Stdin = os.Stdinsehingga seolah-olah Anda benar-benar telah menjalankan perintah itu dari shell Anda.
Nucleon
4
Bagi mereka yang ingin mengalihkan logalih-alih stdout, ada jawabannya di sini
Rick Smith
18

Saya yakin jika Anda mengimpor iodan osdan mengganti ini:

//fmt.Println(out)

dengan ini:

go io.Copy(os.Stdout, out)

(lihat dokumentasi untukio.Copy dan untukos.Stdout ), itu akan melakukan apa yang Anda inginkan. (Penafian: tidak diuji.)

Ngomong-ngomong, Anda mungkin ingin merekam kesalahan standar juga, dengan menggunakan pendekatan yang sama seperti untuk keluaran standar, tetapi dengan cmd.StderrPipedan os.Stderr.

ruakh
sumber
2
@mbert: Saya telah menggunakan cukup banyak bahasa lain, dan telah cukup membaca tentang Go, untuk memiliki firasat tentang fitur apa yang mungkin ada untuk melakukan ini, dan kira-kira dalam bentuk apa; kemudian saya hanya perlu melihat-lihat dokumen paket yang relevan (ditemukan oleh Googling) untuk memastikan bahwa firasat saya benar, dan untuk menemukan detail yang diperlukan. Bagian tersulit adalah (1) menemukan apa yang disebut keluaran standar ( os.Stdout) dan (2) mengkonfirmasikan premis bahwa, jika Anda tidak memanggil cmd.StdoutPipe()sama sekali, keluaran standar pergi ke /dev/nulldaripada ke keluaran standar proses induk .
ruakh
15

Bagi mereka yang tidak membutuhkan ini dalam satu loop, tetapi ingin output perintah bergema ke terminal tanpa cmd.Wait()memblokir pernyataan lain:

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
sumber
Informasi kecil: (Jelas) Anda mungkin melewatkan hasil goroutine yang dimulai jika "lakukan hal lain di sini" selesai lebih cepat daripada goroutine. Keluar main () akan menyebabkan goroutine berakhir juga. sehingga Anda berpotensi tidak benar-benar mengeluarkan output ke gema di terminal jika Anda tidak menunggu cmd selesai.
galaktor