带有修改后的 Stdin 的 exec.Wait() 无限期等待

我在使用修改后的 Stdin 时遇到了 exec.Wait() 的奇怪行为。我只是修改 Stdin,以便能够复制其内容、计算数据量……但这不是这里的问题。

我制作这个精简程序只是为了演示奇怪的行为:

  • 使用修改后的标准输入,cmd.Wait()无限期地等待......直到我按“enter”或“^C”

  • 使用未经修改的标准输入(取消注释该行cmd.Stdin = os.Stdin),程序可以完美地处理到最后。

  • 当我用 Delve () 启动这个程序(使用修改后的标准输入)时dlv debug,程序完美地处理到最后!

  • 我还在和之间添加了time.Sleep30 秒,然后将程序附加到 Delve ( )。当我输入 时,无限期地等待......直到我按“enter”或“^C”cmd.Start()cmd.Wait()dlv attach PIDcontinuecmd.Wait()

我用 go1.11 和 go1.12 测试了这些行为

package main


import (

    "fmt"

    "os"

    "os/exec"

)


type Splitter struct {

    f  *os.File

    fd int

}


func NewSplitter(f *os.File) *Splitter {

    return &Splitter{f, int(f.Fd())}

}


func (s *Splitter) Close() error {

    return s.f.Close()

}


func (s *Splitter) Read(p []byte) (int, error) {

    return s.f.Read(p)

}


func (s *Splitter) Write(p []byte) (int, error) {

    return s.f.Write(p)

}


func main() {

    var cmd *exec.Cmd

    cmd = exec.Command("cat", "foobarfile")

    cmd.Stdin = NewSplitter(os.Stdin)

    //cmd.Stdin = os.Stdin

    cmd.Stdout = NewSplitter(os.Stdout)

    cmd.Stderr = NewSplitter(os.Stderr)

    cmd.Start()

    cmd.Wait()

    fmt.Println("done")

}

我做错了什么吗?


感谢您的帮助。


心有法竹
浏览 92回答 2
2回答

白猪掌柜的

该程序会按照您的要求复制内容。您也可以尝试评论部分。这些评论是不言自明的,我希望它能解释您的疑问。package mainimport (    "io"    "log"    "os"    "os/exec")func main() {    // Execute cat command w/ arguments    // cmd := exec.Command("cat", "hello.txt")    // Execute cat command w/o arguments    cmd := exec.Command("cat")    // Attach STDOUT stream    stdout, err := cmd.StdoutPipe()    if err != nil {        log.Println(err)    }    // Attach STDIN stream    stdin, err := cmd.StdinPipe()    if err != nil {        log.Println(err)    }    // Attach STDERR stream    stderr, err := cmd.StderrPipe()    if err != nil {        log.Println(err)    }    // Spawn go-routine to copy os's stdin to command's stdin    go io.Copy(stdin, os.Stdin)    // Spawn go-routine to copy command's stdout to os's stdout    go io.Copy(os.Stdout, stdout)    // Spawn go-routine to copy command's stderr to os's stderr    go io.Copy(os.Stderr, stderr)    // Run() under the hood calls Start() and Wait()    cmd.Run()    // Note: The PIPES above will be closed automatically after Wait sees the command exit.    // A caller need only call Close to force the pipe to close sooner.    log.Println("Command complete")}

精慕HU

您正在用其他 Go 类型替换进程文件描述符(通常为 )*os.File。为了让 stdin 像流一样工作,os/exec包需要启动一个 goroutine 在io.Reader进程之间复制数据。这在os/exec包中记录:// Otherwise, during the execution of the command a separate// goroutine reads from Stdin and delivers that data to the command// over a pipe. In this case, Wait does not complete until the goroutine// stops copying, either because it has reached the end of Stdin// (EOF or a read error) or because writing to the pipe returned an error.如果您查看程序的堆栈跟踪,您会发现它正在等待 io goroutine 在以下位置完成Wait():goroutine 1 [chan receive]:os/exec.(*Cmd).Wait(0xc000076000, 0x0, 0x0)    /usr/local/go/src/os/exec/exec.go:510 +0x125main.main()因为您现在可以控制数据流,所以您可以根据需要关闭它。如果这里不需要 Stdin,那么根本就不要分配它。如果要使用它,那么您必须Close()将其Wait()归还。另一种选择是确保您使用的是*os.File,最简单的方法是使用StdinPipe,StdoutPipe和StderrPipe方法,后者又使用os.Pipe(). 这种方式确保进程只处理*os.File,而不处理其他 Go 类型。
打开App,查看更多内容
随时随地看视频慕课网APP