เปลี่ยนเส้นทางท่อ stdout ของกระบวนการย่อยใน Go


106

ฉันกำลังเขียนโปรแกรมใน Go ซึ่งรันเซิร์ฟเวอร์เช่นโปรแกรม (เช่น Go) ตอนนี้ฉันต้องการให้ stdout ของโปรแกรมลูกในหน้าต่างเทอร์มินัลของฉันที่ฉันเริ่มโปรแกรมหลัก วิธีหนึ่งในการทำเช่นนี้คือการใช้cmd.Output()ฟังก์ชัน แต่จะพิมพ์ stdout หลังจากออกจากกระบวนการแล้วเท่านั้น (นั่นเป็นปัญหาเนื่องจากโปรแกรมที่เหมือนเซิร์ฟเวอร์นี้ทำงานเป็นเวลานานและฉันต้องการอ่านเอาต์พุตบันทึก)

ตัวแปรoutเป็นของtype io.ReadCloserและฉันไม่รู้ว่าควรทำอย่างไรกับมันเพื่อให้บรรลุภารกิจของฉันและฉันไม่พบสิ่งที่เป็นประโยชน์บนเว็บในหัวข้อนี้

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

คำอธิบายเกี่ยวกับรหัส: ยกเลิกการใส่เครื่องหมายPrintlnฟังก์ชันเพื่อรับโค้ดเพื่อคอมไพล์ฉันรู้ว่านั่นPrintln(out io.ReadCloser)ไม่ใช่ฟังก์ชันที่มีความหมาย
(สร้างผลลัพธ์&{3 |0 <nil> 0}) สองบรรทัดนี้จำเป็นเพื่อรับโค้ดเพื่อคอมไพล์


1
บรรทัดคำสั่งการนำเข้า "exec" ของคุณควรเป็น "os / exec"
evilspacepirate

ขอบคุณสำหรับข้อมูลจริงๆแล้วมันเป็นเพียง exec pre go1 ตอนนี้อยู่ในระบบปฏิบัติการ อัปเดตสำหรับ go1
mbert

1
ฉันไม่คิดว่าคุณจะต้องโทรio.Copyไปภายในกิจวัตร
จริงๆ

ฉันไม่คิดว่าคุณต้องโทรcmd.Wait()หรือfor{}วนซ้ำ ... ทำไมถึงมาอยู่ที่นี่?
weberc2

@ weberc2 สำหรับรูปลักษณ์นี้เพื่อกำจัดคำตอบของ ไม่จำเป็นต้องใช้ for loop หากคุณต้องการเรียกใช้โปรแกรมเพียงครั้งเดียว แต่ถ้าคุณไม่เรียก cmd รอ () main () ของคุณอาจสิ้นสุดก่อนที่โปรแกรมที่คุณเรียกจะเสร็จสิ้นและคุณจะไม่ได้ผลลัพธ์ที่ต้องการ
mbert

คำตอบ:


208

ตอนนี้ฉันต้องการให้ stdout ของโปรแกรมลูกในหน้าต่างเทอร์มินัลของฉันที่ฉันเริ่มโปรแกรมหลัก

ไม่ต้องยุ่งกับท่อหรือ goroutines อันนี้ง่ายมาก

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
นอกจากนี้หากคุณต้องการให้คำสั่งรับฟังอินพุตคุณสามารถตั้งค่าได้โดยcmd.Stdin = os.Stdinทำให้เหมือนกับว่าคุณได้รันคำสั่งนั้นจากเชลล์ของคุณอย่างแท้จริง
Nucleon

4
สำหรับผู้ที่ต้องการเปลี่ยนเส้นทางไปlogแทนที่จะเป็น stdout มีคำตอบที่นี่
Rick Smith

18

ผมเชื่อว่าถ้าคุณนำเข้าioและosและแทนที่นี้:

//fmt.Println(out)

ด้วยสิ่งนี้:

go io.Copy(os.Stdout, out)

(ดูเอกสารสำหรับio.Copyและสำหรับos.Stdout ) มันจะทำสิ่งที่คุณต้องการ (ข้อจำกัดความรับผิดชอบ: ไม่ผ่านการทดสอบ)

โดยวิธีการที่คุณอาจจะต้องการที่จะจับมาตรฐานข้อผิดพลาดเช่นกันโดยใช้วิธีการเช่นเดียวกับมาตรฐานการส่งออก แต่ด้วยและcmd.StderrPipeos.Stderr


2
@mbert: ฉันใช้ภาษาอื่นมามากพอแล้วและอ่านเกี่ยวกับ Go มามากพอแล้วมีลางสังหรณ์ว่าคุณลักษณะใดน่าจะมีอยู่ในการทำสิ่งนี้และในรูปแบบใดโดยประมาณ จากนั้นฉันก็ต้องดูเอกสารแพ็คเกจที่เกี่ยวข้อง (ค้นพบโดย Googling) เพื่อยืนยันว่าลางสังหรณ์ของฉันถูกต้องและเพื่อค้นหารายละเอียดที่จำเป็น ส่วนที่ยากที่สุดคือ (1) การค้นหาว่าเอาต์พุตมาตรฐานเรียกว่าอะไร ( os.Stdout) และ (2) ยืนยันหลักฐานว่าหากคุณไม่เรียกcmd.StdoutPipe()เลยเอาต์พุตมาตรฐานจะไป/dev/nullแทนเอาต์พุตมาตรฐานของกระบวนการหลัก .
ruakh

15

สำหรับผู้ที่ไม่ต้องการสิ่งนี้ในการวนซ้ำ แต่ต้องการให้เอาต์พุตคำสั่งสะท้อนไปที่เทอร์มินัลโดยไม่ต้องcmd.Wait()ปิดกั้นคำสั่งอื่น:

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: (แน่นอน) คุณอาจพลาดผลลัพธ์ของ goroutines ที่เริ่มต้นถ้าคุณ "ทำอย่างอื่นที่นี่" เสร็จเร็วกว่า goroutines การออกจาก main () จะทำให้ goroutines สิ้นสุดลงเช่นกัน ดังนั้นคุณอาจไม่สามารถแสดงผลเป็น echo ในเทอร์มินัลได้หากคุณไม่รอให้ cmd เสร็จสิ้น
galaktor
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.